From 88b95cea4c51dac0ea1464fa8212f50cfe6c7a4d Mon Sep 17 00:00:00 2001 From: feng lv Date: Fri, 1 Sep 2017 14:33:04 +0800 Subject: [PATCH] translated --- .../tech/20170813 An Intro to Compilers.md | 230 ------------------ .../tech/20170813 An Intro to Compilers.md | 219 +++++++++++++++++ 2 files changed, 219 insertions(+), 230 deletions(-) delete mode 100644 sources/tech/20170813 An Intro to Compilers.md create mode 100644 translated/tech/20170813 An Intro to Compilers.md diff --git a/sources/tech/20170813 An Intro to Compilers.md b/sources/tech/20170813 An Intro to Compilers.md deleted file mode 100644 index 7cab541a69..0000000000 --- a/sources/tech/20170813 An Intro to Compilers.md +++ /dev/null @@ -1,230 +0,0 @@ -ucasFL translating - -An Intro to Compilers -============================================================ - -### How to Speak to Computers, Pre-Siri - - -A compiler is just a program that translates other programs. Traditional compilers translate source code into executable machine code that your computer understands. (Some compilers translate source code into another programming language. These compilers are called source-to-source translators or transpilers.) [LLVM][7] is a widely used compiler project, consisting of many modular compiler tools. - -Traditional compiler design comprises three parts:  -![](https://nicoleorchard.com/img/blog/compilers/compiler1.jpg) - -* The Frontend translates source code into an intermediate representation (IR)*. [`clang`][1] is LLVM’s frontend for the C family of languages. - -* The Optimizer analyzes the IR and translates it into a more efficient form. [`opt`][2] is the LLVM optimizer tool. - -* The Backend generates machine code by mapping the IR to the target hardware instruction set. [`llc`][3] is the LLVM backend tool. - -* LLVM IR is a low-level language that is similar to assembly. However, it abstracts away hardware-specific information. - -### Hello, Compiler 👋 - -Below is a simple C program that prints “Hello, Compiler!” to stdout. The C syntax is human-readable, but my computer wouldn’t know what to do with it. I’m going to walk through the three compilation phases to make this program machine-executable. - -``` -// compile_me.c -// Wave to the compiler. The world can wait. - -#include - -int main() { - printf("Hello, Compiler!\n"); - return 0; -} -``` - -### The Frontend - -As I mentioned above, `clang` is LLVM’s frontend for the C family of languages. Clang consists of a C preprocessor, lexer, parser, semantic analyzer, and IR generator. - -* The C Preprocessor modifies the source code before beginning the translation to IR. The preprocessor handles including external files, like `#include ` above. It will replace that line with the entire contents of the `stdio.h` C standard library file, which will include the declaration of the `printf` function. - - _See the output of the preprocessor step by running:_ - - ``` - clang -E compile_me.c -o preprocessed.i - - ``` - -* The Lexer (or scanner or tokenizer) converts a string of characters to a string of words. Each word, or token, is assigned to one of five syntactic categories: punctuation, keyword, identifier, literal, or comment. - - _Tokenization of compile_me.c_   - ![](https://nicoleorchard.com/img/blog/compilers/lexer.jpg) - -* The Parser determines whether or not the stream of words consists of valid sentences in the source language. After analyzing the grammar of the token stream, it outputs an abstract syntax tree (AST). Nodes in a Clang AST represent declarations, statements, and types. - - _The AST of compile_me.c_ - -![](https://nicoleorchard.com/img/blog/compilers/tree.jpg) - -* The Semantic Analyzer traverses the AST, determining if code sentences have valid meaning. This phase checks for type errors. If the main function in compile_me.c returned `"zero"` instead of `0`, the semantic analyzer would throw an error because `"zero"` is not of type `int`. - -* The IR Generator translates the AST to IR. - - _Run the clang frontend on compile_me.c to generate LLVM IR:_ - - ``` - clang -S -emit-llvm -o llvm_ir.ll compile_me.c - - ``` - - _The main function in llvm_ir.ll_ - - ``` - ; llvm_ir.ll - @.str = private unnamed_addr constant [18 x i8] c"Hello, Compiler!\0A\00", align 1 - - define i32 @main() { - %1 = alloca i32, align 4 ; <- memory allocated on the stack - store i32 0, i32* %1, align 4 - %2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str, i32 0, i32 0)) - ret i32 0 - } - - declare i32 @printf(i8*, ...) - ``` - -### The Optimizer - -The job of the optimizer is to improve code efficiency based on its understanding of the program’s runtime behavior. The optimizer takes IR as input and produces improved IR as output. LLVM’s optimizer tool, `opt`, will optimize for processor speed with the flag `-O2` (capital o, two) and for size with the flag `-Os` (capital o, s). - -Take a look at the difference between the LLVM IR code our frontend generated above and the result of running: - -``` -opt -O2 -S llvm_ir.ll -o optimized.ll - -``` - - _The main function in optimized.ll_ - -``` -optimized.ll - -@str = private unnamed_addr constant [17 x i8] c"Hello, Compiler!\00" - -define i32 @main() { - %puts = tail call i32 @puts(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @str, i64 0, i64 0)) - ret i32 0 -} - -declare i32 @puts(i8* nocapture readonly) - -``` - -In the optimized version, main doesn’t allocate memory on the stack, since it doesn’t use any memory. The optimized code also calls `puts` instead of `printf`because none of `printf`’s formatting functionality was used. - -Of course, the optimizer does more than just know when to use `puts` in lieu of `printf`. The optimizer also unrolls loops and inlines the results of simple calculations. Consider the program below, which adds two integers and prints the result. - -``` -// add.c -#include - -int main() { - int a = 5, b = 10, c = a + b; - printf("%i + %i = %i\n", a, b, c); -} -``` - - _Here is the unoptimized LLVM IR:_ - -``` -@.str = private unnamed_addr constant [14 x i8] c"%i + %i = %i\0A\00", align 1 - -define i32 @main() { - %1 = alloca i32, align 4 ; <- allocate stack space for var a - %2 = alloca i32, align 4 ; <- allocate stack space for var b - %3 = alloca i32, align 4 ; <- allocate stack space for var c - store i32 5, i32* %1, align 4 ; <- store 5 at memory location %1 - store i32 10, i32* %2, align 4 ; <- store 10 at memory location %2 - %4 = load i32, i32* %1, align 4 ; <- load the value at memory address %1 into register %4 - %5 = load i32, i32* %2, align 4 ; <- load the value at memory address %2 into register %5 - %6 = add nsw i32 %4, %5 ; <- add the values in registers %4 and %5\. put the result in register %6 - store i32 %6, i32* %3, align 4 ; <- put the value of register %6 into memory address %3 - %7 = load i32, i32* %1, align 4 ; <- load the value at memory address %1 into register %7 - %8 = load i32, i32* %2, align 4 ; <- load the value at memory address %2 into register %8 - %9 = load i32, i32* %3, align 4 ; <- load the value at memory address %3 into register %9 - %10 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i32 %7, i32 %8, i32 %9) - ret i32 0 -} - -declare i32 @printf(i8*, ...) - -``` - - _Here is the optimized LLVM IR:_ - -``` -@.str = private unnamed_addr constant [14 x i8] c"%i + %i = %i\0A\00", align 1 - -define i32 @main() { - %1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i64 0, i64 0), i32 5, i32 10, i32 15) - ret i32 0 -} - -declare i32 @printf(i8* nocapture readonly, ...) - -``` - -Our optimized main function is essentially lines 17 and 18 of the unoptimized version, with the variable values inlined. `opt` calculated the addition because all of the variables were constant. Pretty cool, huh? - -### The Backend - -LLVM’s backend tool is `llc`. It generates machine code from LLVM IR input in three phases: - -* Instruction selection is the mapping of IR instructions to the instruction-set of the target machine. This step uses an infinite namespace of virtual registers. - -* Register allocation is the mapping of virtual registers to actual registers on your target architecture. My CPU has an x86 architecture, which is limited to 16 registers. However, the compiler will use as few registers as possible. - -* Instruction scheduling is the reordering of operations to reflect the target machine’s performance constraints. - - _Running this command will produce some machine code!_ - -``` -llc -o compiled-assembly.s optimized.ll - -``` - -``` -_main: - pushq %rbp - movq %rsp, %rbp - leaq L_str(%rip), %rdi - callq _puts - xorl %eax, %eax - popq %rbp - retq -L_str: - .asciz "Hello, Compiler!" - -``` - -This program is x86 assembly language, which is the human readable syntax for the language my computer speaks. Someone finally understands me 🙌 - -* * * - -Resources - -1. [Engineering a compiler][4] - -2. [Getting Started with LLVM Core Libraries][5] - --------------------------------------------------------------------------------- - -via: https://nicoleorchard.com/blog/compilers - -作者:[Nicole Orchard ][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://nicoleorchard.com/ -[1]:http://clang.llvm.org/ -[2]:http://llvm.org/docs/CommandGuide/opt.html -[3]:http://llvm.org/docs/CommandGuide/llc.html -[4]:https://www.amazon.com/Engineering-Compiler-Second-Keith-Cooper/dp/012088478X -[5]:https://www.amazon.com/Getting-Started-LLVM-Core-Libraries/dp/1782166920 -[6]:https://twitter.com/norchard/status/864246049266958336 -[7]:http://llvm.org/ diff --git a/translated/tech/20170813 An Intro to Compilers.md b/translated/tech/20170813 An Intro to Compilers.md new file mode 100644 index 0000000000..f4dc8ec31a --- /dev/null +++ b/translated/tech/20170813 An Intro to Compilers.md @@ -0,0 +1,219 @@ +编译器简介 +============================================================ + +### 如何对计算机说话 - Pre-Siri + +简单说来,一个编译器不过是一个可以翻译其他程序的程序。传统的编译器可以把源代码翻译成你的机器能够理解的可执行机器代码。(一些编译器将源代码翻译成别的程序语言,这样的编译器称为源到源翻译器或转化器。)[LLVM][7] 是一个广泛使用的编译器项目,包含许多模块化的编译工具。 + +传统的编译器设计包含三个部分: + +![](https://nicoleorchard.com/img/blog/compilers/compiler1.jpg) + +* 通过前端翻译将源代码转化为中间表示: (IR)* 。[`clang`][1] 是 LLVM 中用于 C 家族语言的前端工具。 +* 优化程序分析指令然后将其转化为更高效的形式。[`opt`][2]是 LLVM 的优化工具。 +* 后端工具通过将指令映射到目标硬件指令集从而生成机器代码。[`11c`][3]是 LLVM 的后端工具。 +* LLVM IR 是一种和汇编类似的低级语言。然而,它抽象出了特定硬件信息。 + +### Hello, Compiler + +下面是一个打印 "Hello, Compiler!" 到标准输出的简单 C 程序。C 语法是人类可读的,但是计算机却不能理解,不知道该程序要干什么。我将通过三个编译阶段使该程序变成机器可执行的程序。 + +``` +// compile_me.c +// Wave to the compiler. The world can wait. + +#include + +int main() { + printf("Hello, Compiler!\n"); + return 0; +} +``` + +### 前端 + +正如我在上面所提到的,`clang` 是 LLVM 中用于 C 家族语言的前端工具。Clang 包含一个 C 预处理器、词法分析器、语法解析器、语义分析器和 IR生成器。 + +* C 预处理器在将源程序翻译成 IR 前修改源程序。预处理器处理外部包含文件,比如上面的 `#include `。 它将会把这一行替换为 `stdio.h` C 标准库文件的完整内容,其中包含 `printf` 函数的声明。 + + *通过运行下面的命令来查看预处理步骤的输出:* + + ​``` + clang -E compile_me.c -o preprocessed.i + + ​``` + +* 词法分析器(或扫描器或分词器)将一串字符转化为一串单词。每一个单词或记号,被归并到五种语法目录中的一个:标点符号、关键字、标识符、文字或注释。 + + *compile_me.c 的分词过程* + ![](https://nicoleorchard.com/img/blog/compilers/lexer.jpg) + +* 语法分析器确定源程序中的单词流是否组成了合法的句子。在分析标识符流的语法后,它会输出一个抽象语法树(AST)。在 Clang 的 AST 中的节点表示声明、语句和类型。 + + _compile_me.c 的语法树_ + +![](https://nicoleorchard.com/img/blog/compilers/tree.jpg) + +* 语义分析器遍历抽象语法树,从而确定代码语句是否有正确意义。这个阶段会检查类型错误。如果 compile_me.c 的 main 函数返回 `"zero"`而不是 `0`, 那么语义分析器将会抛出一个错误,因为 `"zero"` 不是 `int` 类型。 + +* IR 生成器将抽象语法树翻译为 IR 。 + + *对 compile_me.c 运行 clang 来生成 LLVM IR:* + + ​``` + clang -S -emit-llvm -o llvm_ir.ll compile_me.c + + ​``` + + 在 llvm_ir.ll 中的 main 函数 + + ​``` + ; llvm_ir.ll + @.str = private unnamed_addr constant [18 x i8] c"Hello, Compiler!\0A\00", align 1 + + define i32 @main() { + %1 = alloca i32, align 4 ; <- memory allocated on the stack + store i32 0, i32* %1, align 4 + %2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str, i32 0, i32 0)) + ret i32 0 + } + + declare i32 @printf(i8*, ...) + ​``` + +### 优化程序 + +优化程序的工作是基于程序的运行时行为来提高代码效率。优化程序将 IR 作为输入然后生成改进后的 IR 作为输出。LLVM 的优化工具 opt 将会通过标记 `-O2`(大写 o,数字 2)来优化处理器速度,通过标记 `Os`(大写 o,小写 s)来减少指令数目。 + +看一看上面的前端工具生成的 LLVM IR 代码和运行下面的命令生成的结果之间的区别: + +``` +opt -O2 -S llvm_ir.ll -o optimized.ll + +``` + + _在 optimized.ll 中的 main 函数_ + +``` +optimized.ll + +@str = private unnamed_addr constant [17 x i8] c"Hello, Compiler!\00" + +define i32 @main() { + %puts = tail call i32 @puts(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @str, i64 0, i64 0)) + ret i32 0 +} + +declare i32 @puts(i8* nocapture readonly) +``` + +优化后的版本中, main 函数没有在栈中分配内存,因为它不使用任何内存。优化后的代码中调用 `puts` 函数而不是 `printf` 函数,因为程序中并没有使用 `printf` 函数的格式化功能。 + +当然,优化程序不仅仅知道何时可以把 `printf` 函数用 `puts` 函数代替。优化程序也能展开循环和内联简单计算的结果。考虑下面的程序,它将两个整数相加并打印出结果。 + +``` +// add.c +#include + +int main() { + int a = 5, b = 10, c = a + b; + printf("%i + %i = %i\n", a, b, c); +} +``` + +_下面是未优化的 LLVM IR:_ + +``` +@.str = private unnamed_addr constant [14 x i8] c"%i + %i = %i\0A\00", align 1 + +define i32 @main() { + %1 = alloca i32, align 4 ; <- allocate stack space for var a + %2 = alloca i32, align 4 ; <- allocate stack space for var b + %3 = alloca i32, align 4 ; <- allocate stack space for var c + store i32 5, i32* %1, align 4 ; <- store 5 at memory location %1 + store i32 10, i32* %2, align 4 ; <- store 10 at memory location %2 + %4 = load i32, i32* %1, align 4 ; <- load the value at memory address %1 into register %4 + %5 = load i32, i32* %2, align 4 ; <- load the value at memory address %2 into register %5 + %6 = add nsw i32 %4, %5 ; <- add the values in registers %4 and %5\. put the result in register %6 + store i32 %6, i32* %3, align 4 ; <- put the value of register %6 into memory address %3 + %7 = load i32, i32* %1, align 4 ; <- load the value at memory address %1 into register %7 + %8 = load i32, i32* %2, align 4 ; <- load the value at memory address %2 into register %8 + %9 = load i32, i32* %3, align 4 ; <- load the value at memory address %3 into register %9 + %10 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i32 %7, i32 %8, i32 %9) + ret i32 0 +} + +declare i32 @printf(i8*, ...) +``` + +_下面是优化后的 LLVM IR_ + +``` +@.str = private unnamed_addr constant [14 x i8] c"%i + %i = %i\0A\00", align 1 + +define i32 @main() { + %1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i64 0, i64 0), i32 5, i32 10, i32 15) + ret i32 0 +} + +declare i32 @printf(i8* nocapture readonly, ...) +``` + +优化后的 main 函数本质上是未优化版本的第 17 行和 18 行,伴有变量值内联。`opt` 计算加法,因为所有的变量都是常数。很酷吧,对不对? + +### 后端 + +LLVM 的后端工具是 `11c`。它分三个阶段将 LLVM IR 作为输入生成机器代码。 + +* 指令选择是将 IR 指令映射到目标机器的指令集。这个步骤使用虚拟寄存器的无限名字空间。 +* 寄存器分配是将虚拟寄存器映射到目标体系结构的实际寄存器。我的 CPU 是 x86 结构,它只有 16 个寄存器。然而,编译器将会尽可能少的使用寄存器。 +* 指令安排是重排操作,从而反映出目标机器的性能约束。 + +_运行下面这个命令将会产生一些机器代码:_ + +``` +llc -o compiled-assembly.s optimized.ll + +``` + +``` +_main: + pushq %rbp + movq %rsp, %rbp + leaq L_str(%rip), %rdi + callq _puts + xorl %eax, %eax + popq %rbp + retq +L_str: + .asciz "Hello, Compiler!" +``` + +这个程序是 x86 汇编语言,它是计算机所说的语言,并具有人类可读语法。某些人最后也许能理解我。 + +* * * + +相关资源: + +1. [设计一个编译器][4] + +2. [开始探索 LLVM 核心库][5] + +-------------------------------------------------------------------------------- + +via: https://nicoleorchard.com/blog/compilers + +作者:[Nicole Orchard][a] +译者:[ucasFL](https://github.com/ucasFL) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://nicoleorchard.com/ +[1]:http://clang.llvm.org/ +[2]:http://llvm.org/docs/CommandGuide/opt.html +[3]:http://llvm.org/docs/CommandGuide/llc.html +[4]:https://www.amazon.com/Engineering-Compiler-Second-Keith-Cooper/dp/012088478X +[5]:https://www.amazon.com/Getting-Started-LLVM-Core-Libraries/dp/1782166920 +[6]:https://twitter.com/norchard/status/864246049266958336 +[7]:http://llvm.org/ \ No newline at end of file