Clang是『编译器前端』吗?

在很多博客中,在和其他人的交流中,我常常听到这样描述clang和LLVM:clang是一个编译器前端,生成中间文件交给LLVM处理。依我来看,这样说是不准确的。

只需一个问题即可证明:如果clang是一个编译器前端,为什么它可以直接生成二进制程序呢?

其实,在LLVM社区中,clang不是一个编译器前端,而是一个compiler driver。把源码编译成二进制是一系列的过程,包括编译(使用compiler)、汇编(使用assembler)、链接(使用linker)。clang是一个总体协调整个过程的程序,它会调用compiler、assembler、linker,故称compiler driver。这也说明了clang为什么可以把源码变成二进制程序。在clang中,它所调用的compiler是clang -cc1,诸君可以使用如下命令一试:

(shell) ➜ clang -cc1 --help | head
OVERVIEW: LLVM 'Clang' Compiler: http://clang.llvm.org

USAGE: clang -cc1 [options] file...

OPTIONS:
  -add-plugin <name>      Use the named plugin action in addition to the default action
  -analyze-function <value>
......

clang -cc1是compiler,所谓编译器前端是它的一部分,clang中所包含的LLVM部分(特指作为编译器中后端的部分)也归属clang -cc1,它们的关系如下图所示。因此,“clang是一个编译器前端,生成中间文件交给LLVM处理”这样说比较合适:clang是一个compiler driver,它调用compiler、assembler、linker来完成整个编译流程;clang -cc1是它使用的compiler,clang -cc1的前端把源码变成LLVM IR并交由LLVM处理,LLVM充当compiler的中后端,完成代码优化与生成的工作,并将生成的汇编交由assembler处理。

不仅clang,gcc也是一个compiler driver,通过-v选项详细输出编译过程,如下所示:

(shell) ➜ gcc -O2 -v a.c b.c -o main
...
 /usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu a.c -quiet -dumpbase a.c -mtune=generic -march=x86-64 -auxbase a -O2 -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccK35Okr.s
...
 as -v --64 -o /tmp/ccWTImBu.o /tmp/ccK35Okr.s
...
/usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu b.c -quiet -dumpbase b.c -mtune=generic -march=x86-64 -auxbase b -O2 -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccK35Okr.s
...
as -v --64 -o /tmp/cciCOJLu.o /tmp/ccK35Okr.s
...
/usr/lib/gcc/x86_64-linux-gnu/9/collect2 ... /tmp/ccWTImBu.o /tmp/cciCOJLu.o ... '-o' 'main' ...
...

从输出可以看到gcc调用了cc1(compiler)、as(assembler)、collect2(linker)。

因此,clang不是一个编译器前端,说它是编译器也不准确,至少没有严格符合社区里的规范;它是一个compiler driver,或者driver。