文章目录
前言
Clang的官方网站是 http://clang.llvm.org,它被认为是C家族的LLVM前端。
Clang可能指代三种不同的实体:
- 前端(由Clang程序库实现)。
- 编译器驱动器(由Clang命令和Clang驱动器程序库实现)。
- 实际的编译器(由clang -cc1 命令实现)。clang -cc1中的编译器不单用Clang程序库实现,还大量用到了LLVM程序库来实现编译器的中端和后端,还有集成的汇编器。
本章中主要将clang作为前端,并使用它的程序库构建自己的编译器。
1. Clang的工作流程
前面我们说clang可能指代三种不同的实体:
- 它可以指代编译器前端是因为它实现了词法分析、语法分析、语义分析和中间代码生成这些步骤,并且把它们封装成libclang(模块化)。这些库是为clang外部用户设计的重要接口,可以单独使用,并将他们跟你的项目链接在一起。如下是相关的程序库:
- libclangLex:这个库用于预处理和词法分析,处理宏、标记、pragma构造;
- libclangAST:这个库提供编译、操作、遍历抽象语法树的功能;
- libclangParse:这个库用于解析程序逻辑,利用此法阶段的结果;
- libclangSema:这个库用于语义分析,为AST验证提供Action(动作)。
- libclangAnalysis:这个库包含静态分析的资源;
- libclangRewrite:这个库支持代码重写,为编译器代码重构工具提供基础设施(如LibTooling这个后面再参阅);
- libclangBasic:这个库提供一系列实用工具-内存分配抽象、源代码位置、诊断,等。
-
编译器驱动器,该代码在
clang/tools/driver/driver.cpp
中实现,clang(这里指编译器前端)和llvm前后分离,编译器驱动器将命令行参数解析,并调用实际的编译器clang -cc1
对source进行编译链接。
clang hello.c -o hello
就是调用编译器驱动器;
clang hello.c -###
,可以在驱动器命令行后面加-###
来查看驱动器的调用清单;$ clang main.c -### clang version 15.0.7 Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /usr/local/bin "/usr/local/bin/clang-15" "-cc1" "-triple" "x86_64-unknown-linux-gnu" "-emit-obj" "-mrelax-all" "--mrelax-relocations" "-disable-free" "-clear-ast-before-backend" "-main-file-name" "main.c" "-mrelocation-model" "pic" "-pic-level" "2" "-pic-is-pie" "-mframe-pointer=all" "-fmath-errno" "-ffp-contract=on" "-fno-rounding-math" "-mconstructor-aliases" "-funwind-tables=2" "-target-cpu" "x86-64" "-tune-cpu" "generic" "-mllvm" "-treat-scalable-fixed-error-as-warning" "-debugger-tuning=gdb" "-fcoverage-compilation-dir=/home/zhimin/compiler/Clang-Learning/Lexer" "-resource-dir" "/usr/local/lib/clang/15.0.7" "-internal-isystem" "/usr/local/lib/clang/15.0.7/include" "-internal-isystem" "/usr/local/include" "-internal-isystem" "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include" "-internal-externc-isystem" "/usr/include/x86_64-linux-gnu" "-internal-externc-isystem" "/include" "-internal-externc-isystem" "/usr/include" "-fdebug-compilation-dir=/home/zhimin/compiler/Clang-Learning/Lexer" "-ferror-limit" "19" "-fgnuc-version=4.2.1" "-fcolor-diagnostics" "-faddrsig" "-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o" "/tmp/main-2715fb.o" "-x" "c" "main.c" "/usr/bin/ld" "-pie" "-z" "relro" "--hash-style=gnu" "--eh-frame-hdr" "-m" "elf_x86_64" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-o" "a.out" "/lib/x86_64-linux-gnu/Scrt1.o" "/lib/x86_64-linux-gnu/crti.o" "/usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o" "-L/usr/lib/gcc/x86_64-linux-gnu/9" "-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib64" "-L/lib/x86_64-linux-gnu" "-L/lib/../lib64" "-L/usr/lib/x86_64-linux-gnu" "-L/usr/lib/../lib64" "-L/lib" "-L/usr/lib" "/tmp/main-2715fb.o" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "-lc" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "/usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o" "/lib/x86_64-linux-gnu/crtn.o"
通过上述结果,编译驱动器调用实际的编译器
clang -cc1
将clang和llvm拼接起来实现了整个编译器(注意:ld这里还是使用GNU的,原因是llvm的链接器还没有集成进来,所以clang -cc1,将source code生成目标文件后就终止了)。当然也可以不通过驱动器来调用clang -cc1,结果就是需要自己手动指定系统头文件的位置,通过-I 选项。
在编译驱动器中使用
-Xclang <option>
可以向这个工具输入具体的参数来激活clang -cc1中的某些隐藏技能。例如clang -cc1工具有一个特殊的选项,可以打印Clang抽象语法树(AST)。可以用clang -Xclang -ast-dump main.c
来激活这个功能。当然也可以直接调用clang -cc1而不是驱动器:clang -cc1 -ast-dump main.c
-
实际的编译器:实际的编译器就是上述所说的
clang -cc1
,每次调用clang -cc1都是由一种主要前端Action来控制的。完整的Action集合定义在源文件clang/include/clang/FrontendOptions.h
中,如下:描述了clang -cc1可能执行的不同任务:
选项 -cc1触发cc1_main函数的执行(源代码文件:clang/tools/driver/cc1_main.cpp
)。例如,当通过clang hello.c -o hello间接调用-cc1时,这个函数初始化目标特定的信息,建立诊断基础设施,执行EmitObj活动。这个活动由CodeGenAction(源代码文件:clang/CodeGen/CodeGenAction.h
)实现,ASTFrontendAction的一个子类。此代码实例化了所有的clang和llvm组件,指挥它们生成目标文件 。