├── src ├── fib.ks ├── ch8_main.cpp ├── ch1_lexer.cpp ├── fib.ir ├── include │ └── KaleidoscopeJIT.h ├── ch2_parser.cpp ├── ch3_genIR.cpp ├── ch4_jit.cpp └── ch5_cfg.cpp ├── doc ├── pics │ └── Kaleidoscope05.png ├── Kaleidoscope01.md ├── Kaleidoscope08.md ├── Kaleidoscope10.md ├── Kaleidoscope09.md ├── Kaleidoscope03.md ├── Kaleidoscope04.md ├── Kaleidoscope02.md ├── Kaleidoscope05.md ├── Kaleidoscope07.md └── Kaleidoscope06.md └── README.md /src/fib.ks: -------------------------------------------------------------------------------- 1 | def fib(x) 2 | if x < 3 then 3 | 1 4 | else 5 | fib(x-1)+fib(x-2); 6 | 7 | fib(10) -------------------------------------------------------------------------------- /doc/pics/Kaleidoscope05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0dulo/Kaleidoscope/HEAD/doc/pics/Kaleidoscope05.png -------------------------------------------------------------------------------- /src/ch8_main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by m0dulo on 2022/12/10. 3 | // 4 | 5 | #include 6 | 7 | extern "C" { 8 | double average(double, double); 9 | } 10 | 11 | int main() { 12 | std::cout << "average of 3.0 and 4.0: " << average(3.0, 4.0) << std::endl; 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐲 基于 LLVM 的 Kaleidoscope 语言自制编译器 2 | 基于 LLVM 的 Kaleidoscope 语言自制编译器实践、个人学习体会和相关文档翻译 3 | ## 组织结构 4 | 每章逐步对 Kaleidoscope 语言编译器进行完善 5 | - [第 1 章:Kaleidoscope 语言和词法分析器](doc/Kaleidoscope01.md) 6 | 阐述了目标和想要构建的基本功能。词法分析器是为一门编程语言构建解析器的基础,使用 C++ 实现一个简单的词法分析器 7 | - [第 2 章:实现解析器和 AST](doc/Kaleidoscope02.md) 8 | 介绍了解析器相关技术,以及抽象语法树的构造。关于解析技术,本教程使用的是递归下降分析法和算符优先级分析法 9 | - [第 3 章:LLVM IR 的代码生成](doc/Kaleidoscope03.md) 10 | 介绍了如何基于 AST 生成 LLVM IR,通过一种简单的方法将 LLVM 引入到编译器实现中 11 | - [第 4 章:添加 JIT 和优化器支持](doc/Kaleidoscope04.md) 12 | 基于 LLVM 为 Kaleidoscope 实现 JIT 编译功能,同时加入对于优化器的支持 13 | - [第 5 章:扩展语言:控制流 ControlFlow](doc/Kaleidoscope05.md) 14 | 对 Kaleidoscope 进行语言扩展,实现控制流能力(if 语句和 for 语句)。同时介绍了 SSA 的构造 15 | - [第 6 章:扩展语言:用户定义的运算符](doc/Kaleidoscope06.md) 16 | 对 Kaleidoscope 进行语言扩展,实现自定义运算符能力,允许用户自定义一元运算符和二元运算符(支持运算符优先级) 17 | - [第 7 章:扩展语言:可变变量](doc/Kaleidoscope07.md) 18 | 对 Kaleidoscope 进行语言扩展,实现局部变量和赋值操作符。同时,介绍了一种隐式的方法让 LLVM 自动构造 SSA 19 | - [第 8 章:编译为目标文件](doc/Kaleidoscope08.md) 20 | 介绍了如何基于 LLVM IR 编译生成目标文件 21 | - [第 9 章:调试信息](doc/Kaleidoscope09.md) 22 | 支持调试器,添加调试信息,允许在 Kaleidoscope 函数中设置断点,打印参数变量和调用函数 23 | - [第 10 章:总结展望](doc/Kaleidoscope10.md) 24 | 主要讨论语言扩展的进阶内容,比如指针、垃圾回收、异常、调试、“意大利面堆栈(spaghetti stacks)”(spaghetti stacks也叫 [Parent pointer tree](https://en.wikipedia.org/wiki/Parent_pointer_tree#:~:text=The%20term%20spaghetti%20stack%20is,bindings%20and%20other%20environmental%20features.), 是一种 N 元树数据结构,其中子节点具有指向父节点的指针)的支持等 25 | -------------------------------------------------------------------------------- /src/ch1_lexer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by m0dulo on 2022/12/02. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum Token { 11 | tok_eof = -1, 12 | tok_def = -2, 13 | tok_extern = -3, 14 | tok_identifier = -4, 15 | tok_number = -5 16 | }; 17 | 18 | static std::string IdentifierStr; 19 | static double NumVal; 20 | 21 | static int gettok() { 22 | static int LastChar = ' '; 23 | 24 | while (isspace(LastChar)) 25 | LastChar = getchar(); 26 | 27 | if (isalpha(LastChar)) { 28 | IdentifierStr = LastChar; 29 | while (isalnum(LastChar = getchar())) 30 | IdentifierStr += LastChar; 31 | 32 | if (IdentifierStr == "def") 33 | return tok_def; 34 | if (IdentifierStr == "extern") 35 | return tok_extern; 36 | return tok_identifier; 37 | } 38 | 39 | if (isdigit(LastChar) || LastChar == '.') { 40 | std::string NumStr; 41 | do { 42 | NumStr += LastChar; 43 | LastChar = getchar(); 44 | } while (isdigit(LastChar) || LastChar == '.'); 45 | 46 | NumVal = strtod(NumStr.c_str(), nullptr); 47 | return tok_number; 48 | } 49 | 50 | if (LastChar == '#') { 51 | do 52 | LastChar = getchar(); 53 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 54 | 55 | if (LastChar != EOF) 56 | return gettok(); 57 | } 58 | 59 | if (LastChar == EOF) 60 | return tok_eof; 61 | 62 | int ThisChar = LastChar; 63 | LastChar = getchar(); 64 | return ThisChar; 65 | } 66 | 67 | int main() { 68 | 69 | int tok; 70 | 71 | while (true) { 72 | tok = gettok(); 73 | printf("token: %d\n", tok); 74 | } 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /src/fib.ir: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'my cool jit' 2 | source_filename = "my cool jit" 3 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 4 | 5 | define double @fib(double %x) !dbg !4 { 6 | entry: 7 | %x1 = alloca double, align 8 8 | call void @llvm.dbg.declare(metadata double* %x1, metadata !9, metadata !DIExpression()), !dbg !10 9 | store double %x, double* %x1, align 8 10 | %x2 = load double, double* %x1, align 8, !dbg !11 11 | %cmptmp = fcmp ult double %x2, 3.000000e+00, !dbg !12 12 | %booltmp = uitofp i1 %cmptmp to double, !dbg !12 13 | %ifcond = fcmp one double %booltmp, 0.000000e+00, !dbg !12 14 | br i1 %ifcond, label %then, label %else, !dbg !12 15 | 16 | then: ; preds = %entry 17 | br label %ifcont, !dbg !13 18 | 19 | else: ; preds = %entry 20 | %x3 = load double, double* %x1, align 8, !dbg !14 21 | %subtmp = fsub double %x3, 1.000000e+00, !dbg !15 22 | %calltmp = call double @fib(double %subtmp), !dbg !15 23 | %x4 = load double, double* %x1, align 8, !dbg !16 24 | %subtmp5 = fsub double %x4, 2.000000e+00, !dbg !17 25 | %calltmp6 = call double @fib(double %subtmp5), !dbg !17 26 | %addtmp = fadd double %calltmp, %calltmp6, !dbg !17 27 | br label %ifcont, !dbg !17 28 | 29 | ifcont: ; preds = %else, %then 30 | %iftmp = phi double [ 1.000000e+00, %then ], [ %addtmp, %else ], !dbg !17 31 | ret double %iftmp, !dbg !17 32 | } 33 | 34 | ; Function Attrs: nofree nosync nounwind readnone speculatable willreturn 35 | declare void @llvm.dbg.declare(metadata, metadata, metadata) #0 36 | 37 | define double @__anon_expr() !dbg !18 { 38 | entry: 39 | %calltmp = call double @fib(double 1.000000e+01), !dbg !21 40 | ret double %calltmp, !dbg !21 41 | } 42 | 43 | attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } 44 | 45 | !llvm.module.flags = !{!0} 46 | !llvm.dbg.cu = !{!1} 47 | 48 | !0 = !{i32 2, !"Debug Info Version", i32 3} 49 | !1 = distinct !DICompileUnit(language: DW_LANG_C, file: !2, producer: "Kaleidoscope Compiler", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !3) 50 | !2 = !DIFile(filename: "fib.ks", directory: ".") 51 | !3 = !{} 52 | !4 = distinct !DISubprogram(name: "fib", scope: !2, file: !2, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !8) 53 | !5 = !DISubroutineType(types: !6) 54 | !6 = !{!7, !7} 55 | !7 = !DIBasicType(name: "double", size: 64, encoding: DW_ATE_float) 56 | !8 = !{!9} 57 | !9 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !2, line: 1, type: !7) 58 | !10 = !DILocation(line: 1, scope: !4) 59 | !11 = !DILocation(line: 2, column: 6, scope: !4) 60 | !12 = !DILocation(line: 2, column: 10, scope: !4) 61 | !13 = !DILocation(line: 3, column: 5, scope: !4) 62 | !14 = !DILocation(line: 5, column: 9, scope: !4) 63 | !15 = !DILocation(line: 5, column: 11, scope: !4) 64 | !16 = !DILocation(line: 5, column: 18, scope: !4) 65 | !17 = !DILocation(line: 5, column: 20, scope: !4) 66 | !18 = distinct !DISubprogram(name: "__anon_expr", scope: !2, file: !2, line: 7, type: !19, scopeLine: 7, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !3) 67 | !19 = !DISubroutineType(types: !20) 68 | !20 = !{!7} 69 | !21 = !DILocation(line: 7, column: 5, scope: !18) -------------------------------------------------------------------------------- /src/include/KaleidoscopeJIT.h: -------------------------------------------------------------------------------- 1 | //===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===// 2 | // 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 | // See https://llvm.org/LICENSE.txt for license information. 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | // 7 | //===----------------------------------------------------------------------===// 8 | // 9 | // Contains a simple JIT definition for use in the kaleidoscope tutorials. 10 | // 11 | //===----------------------------------------------------------------------===// 12 | 13 | #ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H 14 | #define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H 15 | 16 | #include "llvm/ADT/StringRef.h" 17 | #include "llvm/ExecutionEngine/JITSymbol.h" 18 | #include "llvm/ExecutionEngine/Orc/CompileUtils.h" 19 | #include "llvm/ExecutionEngine/Orc/Core.h" 20 | #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" 21 | #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" 22 | #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" 23 | #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" 24 | #include "llvm/ExecutionEngine/Orc/TargetProcessControl.h" 25 | #include "llvm/ExecutionEngine/SectionMemoryManager.h" 26 | #include "llvm/IR/DataLayout.h" 27 | #include "llvm/IR/LLVMContext.h" 28 | #include 29 | 30 | namespace llvm { 31 | namespace orc { 32 | 33 | class KaleidoscopeJIT { 34 | private: 35 | std::unique_ptr TPC; 36 | std::unique_ptr ES; 37 | 38 | DataLayout DL; 39 | MangleAndInterner Mangle; 40 | 41 | RTDyldObjectLinkingLayer ObjectLayer; 42 | IRCompileLayer CompileLayer; 43 | 44 | JITDylib &MainJD; 45 | 46 | public: 47 | KaleidoscopeJIT(std::unique_ptr TPC, 48 | std::unique_ptr ES, 49 | JITTargetMachineBuilder JTMB, DataLayout DL) 50 | : TPC(std::move(TPC)), ES(std::move(ES)), DL(std::move(DL)), 51 | Mangle(*this->ES, this->DL), 52 | ObjectLayer(*this->ES, 53 | []() { return std::make_unique(); }), 54 | CompileLayer(*this->ES, ObjectLayer, 55 | std::make_unique(std::move(JTMB))), 56 | MainJD(this->ES->createBareJITDylib("
")) { 57 | MainJD.addGenerator( 58 | cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( 59 | DL.getGlobalPrefix()))); 60 | } 61 | 62 | ~KaleidoscopeJIT() { 63 | if (auto Err = ES->endSession()) 64 | ES->reportError(std::move(Err)); 65 | } 66 | 67 | static Expected> Create() { 68 | auto SSP = std::make_shared(); 69 | auto TPC = SelfTargetProcessControl::Create(SSP); 70 | if (!TPC) 71 | return TPC.takeError(); 72 | 73 | auto ES = std::make_unique(std::move(SSP)); 74 | 75 | JITTargetMachineBuilder JTMB((*TPC)->getTargetTriple()); 76 | 77 | auto DL = JTMB.getDefaultDataLayoutForTarget(); 78 | if (!DL) 79 | return DL.takeError(); 80 | 81 | return std::make_unique(std::move(*TPC), std::move(ES), 82 | std::move(JTMB), std::move(*DL)); 83 | } 84 | 85 | const DataLayout &getDataLayout() const { return DL; } 86 | 87 | JITDylib &getMainJITDylib() { return MainJD; } 88 | 89 | Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) { 90 | if (!RT) 91 | RT = MainJD.getDefaultResourceTracker(); 92 | return CompileLayer.add(RT, std::move(TSM)); 93 | } 94 | 95 | Expected lookup(StringRef Name) { 96 | return ES->lookup({&MainJD}, Mangle(Name.str())); 97 | } 98 | }; 99 | 100 | } // end namespace orc 101 | } // end namespace llvm 102 | 103 | #endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H 104 | -------------------------------------------------------------------------------- /doc/Kaleidoscope01.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:Kaleidoscope介绍与词法分析 2 | 3 | ## Kaleidoscope语言 4 | 5 | 本教程使用一种名为“[Kaleidoscope](http://en.wikipedia.org/wiki/Kaleidoscope)”的玩具语言进行说明(含义是“漂亮、标准和直观”)。Kaleidoscope是一种过程性语言,允许定义函数、使用条件等。后面将扩展Kaleidoscope以支持IF/THEN/ELSE结构、for循环、用户定义的运算符、具有简单命令行界面的JIT编译、调试信息等。 6 | 7 | 为Kaleidoscope中唯一的数据类型是64位浮点类型(双精度)。因此,所有值都是隐式双精度的,并且该语言不需要类型声明。这为该语言提供了一种非常好并且简单的语法。例如,下面的简单示例计算[斐波那契数列:](http://en.wikipedia.org/wiki/Fibonacci_number) 8 | 9 | ``` 10 | # Compute the x'th fibonacci number. 11 | def fib(x) 12 | if x < 3 then 13 | 1 14 | else 15 | fib(x-1)+fib(x-2) 16 | 17 | # This expression will compute the 40th number. 18 | fib(40) 19 | ``` 20 | 21 | Kaleidoscope可以调用标准库函数-LLVM JIT使这一点变得非常容易。这意味着可以在使用函数之前使用‘extern’关键字来定义该函数(这对于相互递归的函数也很有用)。例如: 22 | 23 | ``` 24 | extern sin(arg); 25 | extern cos(arg); 26 | extern atan2(arg1 arg2); 27 | 28 | atan2(sin(.4), cos(42)) 29 | ``` 30 | 31 | ## 词法分析器 32 | 33 | 当谈到实现一种语言时,首先需要的是处理文本文件并识别其内容的能力。执行此操作的传统方法是使用词法分析器(也称为“[lexer](http://en.wikipedia.org/wiki/Lexical_analysis)”)将输入分解为“令牌(token)”。词法分析器返回的每个令牌都包括一个令牌码和一些可能的元数据(例如,数字的数字值)。首先,定义可能性: 34 | 35 | ```c++ 36 | // The lexer returns tokens [0-255] if it is an unknown character, otherwise one 37 | // of these for known things. 38 | enum Token { 39 | tok_eof = -1, 40 | 41 | // commands 42 | tok_def = -2, 43 | tok_extern = -3, 44 | 45 | // primary 46 | tok_identifier = -4, 47 | tok_number = -5, 48 | }; 49 | 50 | static std::string IdentifierStr; // Filled in if tok_identifier 51 | static double NumVal; // Filled in if tok_number 52 | ``` 53 | 54 | 词法分析器返回的每个令牌要么是一个令牌枚举值,要么是一个‘未知’字符,如‘+’,该字符将作为其ASCII值返回。如果当前令牌是标识符,则`IdentifierStr`全局变量保存标识符的名称。如果当前标记是数字常量(如1.0),则`NumVal`保存其值。 55 | 56 | 词法分析器的实际实现是一个名为`gettok`的函数。调用`gettok`函数从标准输入返回下一个令牌。它的定义开始于: 57 | 58 | ```c++ 59 | /// gettok - Return the next token from standard input. 60 | static int gettok() { 61 | static int LastChar = ' '; 62 | 63 | // Skip any whitespace. 64 | while (isspace(LastChar)) 65 | LastChar = getchar(); 66 | ``` 67 | 68 | `gettok`的工作原理是调用C语言中`getchar()`函数从标准输入中一次读取一个字符。它在识别它们时会将其吃掉,并将最后读取但未处理的字符存储在LastChar中。它必须做的第一件事是忽略标记之间的空格。这是通过上面的循环实现的。 69 | 70 | `gettok`需要做的下一件事是识别标识符和特定的关键字,如“def”。Kaleidoscope用这个简单的循环来做这件事: 71 | 72 | ```c++ 73 | if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]* 74 | IdentifierStr = LastChar; 75 | while (isalnum((LastChar = getchar()))) 76 | IdentifierStr += LastChar; 77 | 78 | if (IdentifierStr == "def") 79 | return tok_def; 80 | if (IdentifierStr == "extern") 81 | return tok_extern; 82 | return tok_identifier; 83 | } 84 | ``` 85 | 86 | 请注意,此代码在每次词法分析标识符时设置‘`IdentifierStr`’全局。此外,因为语言关键字由相同的循环匹配,所以在这里内联处理它们。数值相似: 87 | 88 | ```c++ 89 | if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+ 90 | std::string NumStr; 91 | do { 92 | NumStr += LastChar; 93 | LastChar = getchar(); 94 | } while (isdigit(LastChar) || LastChar == '.'); 95 | 96 | NumVal = strtod(NumStr.c_str(), 0); 97 | return tok_number; 98 | } 99 | ``` 100 | 101 | 这些都是用于处理输入的非常简单的代码。从输入读取数值时,我们使用C`strtod`函数将其转换为存储在`NumVal`中的数值。请注意,这没有进行充分的错误检查:它将错误地读取“1.23.45.67”并将其视为您键入的“1.23”。请随意扩展它!接下来,我们将处理注释: 102 | 103 | ```c++ 104 | if (LastChar == '#') { 105 | // Comment until end of line. 106 | do 107 | LastChar = getchar(); 108 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 109 | 110 | if (LastChar != EOF) 111 | return gettok(); 112 | } 113 | ``` 114 | 115 | 跳到行尾来处理注释,然后返回下一个标记。最后,如果输入与上述其中一种情况不匹配,则它要么是操作字符,如‘+’,要么是文件的末尾。这些使用以下代码进行处理: 116 | 117 | ```c++ 118 | // Check for end of file. Don't eat the EOF. 119 | if (LastChar == EOF) 120 | return tok_eof; 121 | 122 | // Otherwise, just return the character as its ascii value. 123 | int ThisChar = LastChar; 124 | LastChar = getchar(); 125 | return ThisChar; 126 | } 127 | ``` 128 | 129 | 这样,我们就有了基本Kaleidoscope语言的完整词法分析器。接下来,就是[构建一个简单的解析器,使用它来构建抽象语法树](Kaleidoscope02.md)。 130 | 131 | 132 | [下一步:实现解析器和AST](Kaleidoscope02.md) 133 | 134 | 135 | ## 后记:心得体会 136 | 本教程仅用到了最基础的c++来完成词法分析部分的工作,相比于用flex做词法分析,是非常简洁的入门教程。后续进阶阅读可以了解下正则表达式以及flex。 -------------------------------------------------------------------------------- /doc/Kaleidoscope08.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:编译成目标代码 2 | 3 | ## 第八章引言 4 | 5 | 本章介绍如何将我们的语言编译成目标文件。 6 | 7 | ## 选择目标 8 | 9 | LLVM具有对交叉编译的原生支持。您可以编译到当前计算机的体系结构,也可以同样轻松地编译到其他体系结构。在本教程中,我们将以当前计算机为目标。 10 | 11 | 为了指定您想要面向的体系结构,我们使用一个名为“目标三元组”的字符串。它的形式为`---`(请参阅[交叉编译docs](https://clang.llvm.org/docs/CrossCompilation.html#target-triple)). 12 | 13 | 举个例子,我们可以看到Clang认为我们目前的目标三元组: 14 | ``` 15 | $ clang --version | grep Target 16 | Target: x86_64-unknown-linux-gnu 17 | ``` 18 | 19 | 运行此命令可能会在您的计算机上显示一些不同的内容,因为您可能正在使用与我不同的架构或操作系统。 20 | 21 | 幸运的是,我们不需要硬编码目标三元组来瞄准当前机器,LLVM提供了`sys::getDefaultTargetTriple`,它返回当前机器的目标三元组。 22 | 23 | ```c++ 24 | auto TargetTriple = sys::getDefaultTargetTriple(); 25 | ``` 26 | 27 | LLVM不要求我们链接所有的目标功能。例如,如果我们只使用JIT,我们就不需要装配printers。同样,如果我们只针对某些架构,我们只能链接那些架构的功能。 28 | 29 | 在本例中,我们将初始化发出object code的所有targets。 30 | 31 | ```c++ 32 | InitializeAllTargetInfos(); 33 | InitializeAllTargets(); 34 | InitializeAllTargetMCs(); 35 | InitializeAllAsmParsers(); 36 | InitializeAllAsmPrinters(); 37 | ``` 38 | 39 | 我们现在可以使用我们的目标三元组来获得一个`Target`: 40 | 41 | ```c++ 42 | std::string Error; 43 | auto Target = TargetRegistry::lookupTarget(TargetTriple, Error); 44 | 45 | // Print an error and exit if we couldn't find the requested target. 46 | // This generally occurs if we've forgotten to initialise the 47 | // TargetRegistry or we have a bogus target triple. 48 | if (!Target) { 49 | errs() << Error; 50 | return 1; 51 | } 52 | ``` 53 | 54 | ## 目标计算机 55 | 56 | 我们还需要一台‘TargetMachine’。这个类提供了我们目标机器的完整机器描述。如果我们想要针对特定的功能(如SSE)或特定的CPU(如Intel的Sandylake),我们现在就可以这么做。 57 | 58 | 要了解LLVM支持哪些功能和CPU,可以使用`llc`。例如,让我们看看x86: 59 | ``` 60 | $ llvm-as < /dev/null | llc -march=x86 -mattr=help 61 | Available CPUs for this target: 62 | 63 | amdfam10 - Select the amdfam10 processor. 64 | athlon - Select the athlon processor. 65 | athlon-4 - Select the athlon-4 processor. 66 | ... 67 | 68 | Available features for this target: 69 | 70 | 16bit-mode - 16-bit mode (i8086). 71 | 32bit-mode - 32-bit mode (80386). 72 | 3dnow - Enable 3DNow! instructions. 73 | 3dnowa - Enable 3DNow! Athlon instructions. 74 | ... 75 | ``` 76 | 77 | 在我们的示例中,我们将使用通用CPU,没有任何附加功能、选项或重新定位模型。 78 | 79 | ```c++ 80 | auto CPU = "generic"; 81 | auto Features = ""; 82 | 83 | TargetOptions opt; 84 | auto RM = Optional(); 85 | auto TargetMachine = Target->createTargetMachine(TargetTriple, CPU, Features, opt, RM); 86 | ``` 87 | 88 | ## 配置模块 89 | 90 | 我们现在已经准备好配置我们的模块,以指定目标和数据布局。这并不是严格需要的,但[前端性能指南](../front end/PerformanceTips.html)建议您这样做。了解目标和数据布局对优化有好处。 91 | 92 | ```c++ 93 | TheModule->setDataLayout(TargetMachine->createDataLayout()); 94 | TheModule->setTargetTriple(TargetTriple); 95 | ``` 96 | 97 | ## 发送对象代码 98 | 99 | 我们已准备好发出目标代码!让我们定义我们要将文件写入的位置: 100 | 101 | ```c++ 102 | auto Filename = "output.o"; 103 | std::error_code EC; 104 | raw_fd_ostream dest(Filename, EC, sys::fs::OF_None); 105 | 106 | if (EC) { 107 | errs() << "Could not open file: " << EC.message(); 108 | return 1; 109 | } 110 | ``` 111 | 112 | 最后,我们定义一个发出对象代码的过程,然后运行该过程: 113 | 114 | ```c++ 115 | legacy::PassManager pass; 116 | auto FileType = CGFT_ObjectFile; 117 | 118 | if (TargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType)) { 119 | errs() << "TargetMachine can't emit a file of this type"; 120 | return 1; 121 | } 122 | 123 | pass.run(*TheModule); 124 | dest.flush(); 125 | ``` 126 | 127 | ## 把这一切放在一起 128 | 129 | 它能用吗?让我们试一试,我们需要编译代码,但是请注意,`llvm-config`的参数与前几章不同。 130 | ``` 131 | $ clang++ -g -O3 toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs all` -o toy 132 | ``` 133 | 134 | 让我们运行它,并定义一个简单的`verage`函数。完成后按Ctrl-D组合键。 135 | ``` 136 | $ ./toy 137 | ready> def average(x y) (x + y) * 0.5; 138 | ^D 139 | Wrote output.o 140 | ``` 141 | 142 | 我们有一个目标文件!为了测试它,让我们编写一个简单的程序,并将其与我们的输出相链接。源代码如下: 143 | 144 | ```c++ 145 | #include 146 | 147 | extern "C" { 148 | double average(double, double); 149 | } 150 | 151 | int main() { 152 | std::cout << "average of 3.0 and 4.0: " << average(3.0, 4.0) << std::endl; 153 | } 154 | ``` 155 | 156 | 我们将程序链接到output.o并检查结果是否符合我们的预期: 157 | ``` 158 | $ clang++ main.cpp output.o -o main 159 | $ ./main 160 | average of 3.0 and 4.0: 3.5 161 | ``` 162 | 163 | 164 | [下一步:添加调试信息](Kaleidoscope09.md) 165 | -------------------------------------------------------------------------------- /doc/Kaleidoscope10.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:总结展望 2 | 3 | ## 教程结论 4 | 5 | 欢迎阅读“[使用LLVM实现语言](index.html)”教程的最后一章。在本教程的过程中,我们已经将我们的小Kaleidoscope语言从一个无用的玩具成长为一个半有趣(但可能仍然没用)的玩具。:) 6 | 7 | 我们构建了整个词法分析器、解析器、AST、代码生成器、交互式Run循环(使用JIT!),并在独立的可执行文件中发出调试信息。 8 | 9 | 我们的小语言支持一些有趣的特性:它支持用户定义的二元和一元运算符,它使用JIT编译进行即时计算,它支持一些带有SSA构造的控制流构造。 10 | 11 | 本教程的部分想法是向您展示定义、构建和使用语言是多么容易和有趣。构建编译器不一定是一个可怕或神秘的过程!既然您已经了解了一些基础知识,我强烈建议您拿起代码并修改它。例如,尝试添加以下内容: 12 | 13 | - **全局变量**-虽然全局变量在现代软件工程中的价值值得怀疑,但在组合像Kaleidoscope编译器本身这样的快速小样例,它们通常很有用。幸运的是,我们当前的设置使得添加全局变量变得非常容易:在拒绝某个未解析的变量之前,只需进行值查找检查它是否在全局变量符号表中。要创建新的全局变量,请创建LLVM`GlobalVariable`类的实例。 14 | - **类型化变量**-Kaleidoscope目前只支持双精度类型的变量。这使该语言非常优雅,因为只支持一种类型意味着您永远不需要指定类型。不同的语言有不同的处理方式。最简单的方法是要求用户为每个变量定义指定类型,并在符号表中记录变量的类型及其值\*。 15 | - **数组、结构、向量等**-一旦添加了类型,就可以开始以各种有趣的方式扩展类型系统。简单数组非常简单,对于许多不同的应用程序非常有用。添加它们主要是为了学习LLVM[getelementptr](../../LangRef.html#getelementptr-instruction)指令是如何工作的:它是如此巧妙/非常规,它[有自己的FAQ页面](../../GetElementPtr.html)! 16 | - **运行时标准**-我们当前的语言允许用户访问任意的外部函数,我们将其用于“printd”和“putchard”。当您扩展语言以添加更高级别的构造时,如果这些构造被降级为对语言提供的运行时的调用,那么这些构造通常是最有意义的。例如,如果您将哈希表添加到语言中,那么将例程添加到运行时可能会有意义,而不是完全内联它们。 17 | - **内存管理**-目前只能在Kaleidoscope中访问堆栈。能够通过调用标准libc malloc/free接口或垃圾收集器来分配堆内存也很有用。如果您想使用垃圾回收,请注意LLVM完全支持[精准垃圾回收](../../GarbageCollection.html),包括移动对象和需要扫描/更新堆栈的算法。 18 | - **异常处理支持**-LLVM支持生成与其他语言编译的代码互操作的[零成本异常](../../ExceptionHandling.html)。您还可以通过隐式地使每个函数返回一个错误值并检查它来生成代码。您还可以显式使用setjmp/long jmp。去这里有很多不同的方式。 19 | - **面向对象,泛型,数据库访问,复数,几何规划,\...** - 真的,有永无止境的疯狂特性可以添加到语言中。 20 | - **不寻常的域**-我们一直在讨论将LLVM应用到一个很多人感兴趣的领域:为特定语言构建编译器。然而,还有许多其他领域可以使用编译器技术,通常不会考虑到这一点。例如,LLVM已经被用来实现OpenGL图形加速,将C++代码翻译成ActionScript,以及其他许多聪明的事情。也许你会是第一个用LLVM将正则表达式解释器编译成本机代码的人? 21 | 22 | 玩得开心--试着做一些疯狂和不同寻常的事情。像其他人一样构建一门语言,比起尝试一些疯狂的或离奇的东西,然后看看结果如何,要无趣得多。如果您遇到困难或想要讨论它,请随时发送电子邮件到[llvm-dev mail list](http://lists.llvm.org/mailman/listinfo/llvm-dev):],它有很多对语言感兴趣的人,并且通常愿意提供帮助。 23 | 24 | 在结束本教程之前,我想谈谈生成LLVM IR的一些“提示和技巧”。这些是一些更微妙的事情,可能不是很明显,但如果您想要利用LLVM的功能,它们是非常有用的。 25 | 26 | ## LLVM IR的性质 27 | 28 | 关于LLVM IR表单中的代码,我们有几个常见的问题-让我们现在就把这些问题解决掉,好吗? 29 | 30 | ### 目标独立性 31 | 32 | Kaleidoscope是“可移植语言”的一个例子:用Kaleidoscope编写的任何程序都可以在它运行的任何目标上以相同的方式工作。许多其他语言都有这个属性,例如LISP、Java、Haskell、javascript、Python等(请注意,虽然这些语言是可移植的,但并不是它们所有的库都是可移植的)。 33 | 34 | LLVM的一个很好的方面是,它通常能够在IR中保持目标独立性:您可以将LLVMIR用于Kaleidoscope编译的程序,并在LLVM支持的任何目标上运行它,甚至发出C代码并在LLVM本地不支持的目标上编译。您可以很容易地看出,Kaleidoscope编译器生成与目标无关的代码,因为它在生成代码时从不查询任何特定于目标的信息。 35 | 36 | LLVM为代码提供了一种紧凑的、与目标无关的表示形式,这一事实让很多人兴奋不已。不幸的是,这些人在询问有关语言可移植性的问题时,通常会想到C或C家族的一种语言。我说“不幸的”,因为除了随身携带源代码之外,确实没有办法使(完全通用的)C代码可移植(当然,C源代码通常也不能移植--曾经将真正的旧应用程序从32位移植到64位吗?)。 37 | 38 | C语言的问题(再说一次,就是它的全部通用性)是它有大量的特定于目标的假设。举一个简单的例子,预处理器在处理输入文本时,通常会从代码中破坏性地删除目标独立性: 39 | 40 | ```c 41 | #ifdef __i386__ 42 | int X = 1; 43 | #else 44 | int X = 42; 45 | #endif 46 | ``` 47 | 48 | 虽然可以设计出越来越复杂的解决方案来解决这类问题,但它不能以比发布实际源代码更好的方式完全解决。 49 | 50 | 也就是说,C语言中有一些有趣的子集可以使其可移植。如果您愿意将原始类型固定为固定大小(例如int=32位,long=64位),不关心ABI与现有二进制文件的兼容性,并且愿意放弃其他一些次要功能,您可以拥有可移植的代码。这对于内核内语言等专门域来说是有意义的。 51 | 52 | ### 安全保障 53 | 54 | 上面的许多语言也是“安全”语言:用Java编写的程序不可能损坏其地址空间并使进程崩溃(假设JVM没有bug)。安全性是一个有趣的属性,需要语言设计、运行时支持,通常还需要操作系统支持。 55 | 56 | 在LLVM中实现安全语言当然是可能的,但是LLVM IR本身并不保证安全。LLVM IR允许不安全的指针强制转换、在释放错误后使用、缓冲区溢出和各种其他问题。安全需要作为LLVM之上的一层来实现,为了方便起见,几个小组已经对此进行了研究。如果您对更多细节感兴趣,请访问[llvm-dev邮件list](http://lists.llvm.org/mailman/listinfo/llvm-dev)]。 57 | 58 | ### 特定于语言的优化 59 | 60 | LLVM让许多人反感的一件事是,它不能在一个系统中解决世界上所有的问题。一个具体的抱怨是,人们认为LLVM无法执行高级语言特定优化:LLVM“丢失了太多信息”。以下是对此的一些观察结果: 61 | 62 | 首先,您说得对,LLVM确实丢失了信息。例如,在撰写本文时,无法在LLVM IR中区分SSA值是来自ILP32机器上的C“int”还是C“long”(调试信息除外)。这两个值都被编译为‘I32’值,并且关于它来自什么的信息也会丢失。这里更普遍的问题是,LLVM类型系统使用“结构等价”而不是“名称等价”。另一个让人惊讶的地方是,如果在高级语言中有两个具有相同结构的类型(例如,两个不同的结构具有单个int字段):这两个类型将编译成单个LLVM类型,并且不可能知道它来自哪里。 63 | 64 | 其次,虽然LLVM确实会丢失信息,但LLVM并不是一个固定的目标:我们在以许多不同的方式继续增强和改进它。除了添加新功能(LLVM并不总是支持异常或调试信息),我们还扩展IR以捕获用于优化的重要信息(例如,参数是符号扩展的还是零扩展的,有关指针别名的信息,等等)。许多增强都是由用户驱动的:人们希望LLVM包含一些特定的特性,所以他们继续扩展它。 65 | 66 | 第三,添加特定于语言的优化是*可能而且容易*,您有很多选择。作为一个简单的例子,很容易添加特定于语言的优化过程,这些优化过程“了解”为一种语言编译的代码。在C系列的情况下,有一个“知道”标准C库函数的优化过程。如果在main()中调用“exit(0)”,它知道将其优化为“return 0;”是安全的,因为C指定了“exit”函数的作用。 67 | 68 | 除了简单的图书馆知识之外,还可以将各种其他语言特定的信息嵌入到LLVM IR中。如果您有特定的需求并遇到困难,请将该主题带到llvm-dev列表中。在最坏的情况下,您可以始终将LLVM视为“哑巴代码生成器”,并在特定于语言的AST上在您的前端实现所需的高级优化。 69 | 70 | ## 小贴士和小窍门 71 | 72 | 在使用LLVM之后,您会了解到许多有用的提示和技巧,这些技巧乍一看并不明显。这一节不是让每个人都重新发现它们,而是讨论其中的一些问题。 73 | 74 | ### 实现可移植的OffsetOf/sizeof 75 | 76 | 如果您试图保持编译器“目标”生成的代码独立,那么就会出现一件有趣的事情,那就是您经常需要知道某个LLVM类型的大小或llvm结构中某个字段的偏移量。例如,您可能需要将类型的大小传递给分配内存的函数。 77 | 78 | 不幸的是,这在不同目标之间可能会有很大差异:例如,指针的宽度与目标无关。然而,有一种使用getelementptr instruction](http://nondot.org/sabre/LLVMNotes/SizeOf-OffsetOf-VariableSizedStructs.txt)的聪明方法,它允许您以可移植的方式进行计算。 79 | 80 | ### 垃圾收集堆栈帧 81 | 82 | 一些语言希望显式地管理它们的堆栈框架,通常是为了对它们进行垃圾回收,或者允许轻松实现闭包。通常有比显式堆栈帧更好的方式来实现这些特性,但是[LLVM确实支持它们,如果您愿意,可以使用](http://nondot.org/sabre/LLVMNotes/ExplicitlyManagedStackFrames.txt)。它需要您的前端将代码转换为[Continue,传递Style](http://en.wikipedia.org/wiki/Continuation-passing_style)并使用尾部调用(LLVM也支持)。 83 | -------------------------------------------------------------------------------- /src/ch2_parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by m0dulo on 2022/11/30. 3 | // 4 | 5 | // #include "llvm/ADT/STLExtras.h" 6 | // #include 7 | include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | enum Token { 17 | tok_eof = -1, 18 | 19 | tok_def = -2, 20 | tok_extern = -3, 21 | 22 | tok_identifier = -4, 23 | tok_number = -5 24 | }; 25 | 26 | static std::string IdentifierStr; 27 | static double NumVal; 28 | 29 | static int gettok() { 30 | static int LastChar = ' '; 31 | 32 | while (isspace(LastChar)) 33 | LastChar = getchar(); 34 | 35 | if (isalpha(LastChar)) { 36 | IdentifierStr = LastChar; 37 | while (isalnum(LastChar = getchar())) 38 | IdentifierStr += LastChar; 39 | 40 | if (IdentifierStr == "def") 41 | return tok_def; 42 | if (IdentifierStr == "extern") 43 | return tok_extern; 44 | return tok_identifier; 45 | } 46 | 47 | if (isdigit(LastChar) || LastChar == '.') { 48 | std::string NumStr; 49 | do { 50 | NumStr += LastChar; 51 | LastChar = getchar(); 52 | } while (isdigit(LastChar) || LastChar == '.'); 53 | 54 | NumVal = strtod(NumStr.c_str(), nullptr); 55 | return tok_number; 56 | } 57 | 58 | if (LastChar == '#') { 59 | do 60 | LastChar = getchar(); 61 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 62 | 63 | if (LastChar != EOF) 64 | return gettok(); 65 | } 66 | 67 | if (LastChar == EOF) 68 | return tok_eof; 69 | 70 | int ThisChar = LastChar; 71 | LastChar = getchar(); 72 | return ThisChar; 73 | } 74 | 75 | 76 | namespace { 77 | 78 | class ExprAST { 79 | public: 80 | virtual ~ExprAST() = default; 81 | }; 82 | 83 | class NumberExprAST : public ExprAST { 84 | double Val; 85 | 86 | public: 87 | NumberExprAST(double Val) : Val(Val) {} 88 | }; 89 | 90 | class VariableExprAST : public ExprAST { 91 | std::string Name; 92 | 93 | public: 94 | VariableExprAST(const std::string &Name) : Name(Name) {} 95 | }; 96 | 97 | class BinaryExprAST : public ExprAST { 98 | char Op; 99 | std::unique_ptr LHS, RHS; 100 | 101 | public: 102 | BinaryExprAST(char Op, std::unique_ptr LHS, 103 | std::unique_ptr RHS) 104 | : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 105 | }; 106 | 107 | class CallExprAST : public ExprAST { 108 | std::string Callee; 109 | std::vector> Args; 110 | 111 | public: 112 | CallExprAST(const std::string &Callee, 113 | std::vector> Args) 114 | : Callee(Callee), Args(std::move(Args)) {} 115 | }; 116 | 117 | class PrototypeAST { 118 | std::string Name; 119 | std::vector Args; 120 | 121 | public: 122 | PrototypeAST(const std::string &Name, std::vector Args) 123 | : Name(Name), Args(std::move(Args)) {} 124 | 125 | const std::string &getName() const { return Name; } 126 | }; 127 | 128 | class FunctionAST { 129 | std::unique_ptr Proto; 130 | std::unique_ptr Body; 131 | 132 | public: 133 | FunctionAST(std::unique_ptr Proto, 134 | std::unique_ptr Body) 135 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 136 | }; 137 | 138 | } 139 | 140 | 141 | static int CurTok; 142 | static int getNextToken() { return CurTok = gettok(); } 143 | 144 | 145 | static std::map BinopPrecedence; 146 | 147 | static int GetTokPrecedence() { 148 | if (!isascii(CurTok)) 149 | return -1; 150 | 151 | int TokPrec = BinopPrecedence[CurTok]; 152 | if (TokPrec <= 0) 153 | return -1; 154 | return TokPrec; 155 | } 156 | 157 | std::unique_ptr LogError(const char *Str) { 158 | fprintf(stderr, "Error: %s\n", Str); 159 | return nullptr; 160 | } 161 | 162 | std::unique_ptr LogErrorP(const char *Str) { 163 | LogError(Str); 164 | return nullptr; 165 | } 166 | 167 | static std::unique_ptr ParseExpression(); 168 | 169 | static std::unique_ptr ParseNumberExpr() { 170 | auto Result = std::make_unique(NumVal); 171 | getNextToken(); 172 | return std::move(Result); 173 | } 174 | 175 | static std::unique_ptr ParseParenExpr() { 176 | getNextToken(); // eat ( 177 | auto V = ParseExpression(); 178 | if (!V) 179 | return nullptr; 180 | 181 | if (CurTok != ')') 182 | return LogError("expected ')'"); 183 | getNextToken(); // eat ')' 184 | return V; 185 | } 186 | 187 | static std::unique_ptr ParseIdentifierExpr() { 188 | std::string IdName = IdentifierStr; 189 | 190 | getNextToken(); 191 | 192 | if (CurTok != '(') 193 | return std::make_unique(IdName); 194 | 195 | getNextToken(); 196 | std::vector> Args; 197 | if (CurTok != ')') { 198 | while (true) { 199 | if (auto Arg = ParseExpression()) 200 | Args.push_back(std::move(Arg)); 201 | else 202 | return nullptr; 203 | 204 | if (CurTok == ')') 205 | break; 206 | 207 | if (CurTok != ',') 208 | return LogError("Expected ')' or ',' in argument list"); 209 | getNextToken(); 210 | } 211 | } 212 | 213 | getNextToken(); // eat ')' 214 | 215 | return std::make_unique(IdName, std::move(Args)); 216 | } 217 | 218 | static std::unique_ptr ParsePrimary() { 219 | switch (CurTok) { 220 | default: 221 | return LogError("unknown token when expecting an expression"); 222 | case tok_identifier: 223 | return ParseIdentifierExpr(); 224 | case tok_number: 225 | return ParseNumberExpr(); 226 | case '(': 227 | return ParseParenExpr(); 228 | } 229 | } 230 | 231 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 232 | std::unique_ptr LHS) { 233 | while (true) { 234 | int TokPrec = GetTokPrecedence(); 235 | 236 | if (TokPrec < ExprPrec) 237 | return LHS; 238 | 239 | int BinOp = CurTok; 240 | getNextToken(); 241 | 242 | auto RHS = ParsePrimary(); 243 | if (!RHS) 244 | return nullptr; 245 | 246 | int NextPrec = GetTokPrecedence(); 247 | if (TokPrec < NextPrec) { 248 | RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS)); 249 | if (!RHS) 250 | return nullptr; 251 | } 252 | 253 | LHS = 254 | std::make_unique(BinOp, std::move(LHS), std::move(RHS)); 255 | } 256 | } 257 | 258 | static std::unique_ptr ParseExpression() { 259 | auto LHS = ParsePrimary(); 260 | if (!LHS) 261 | return nullptr; 262 | 263 | return ParseBinOpRHS(0, std::move(LHS)); 264 | } 265 | 266 | static std::unique_ptr ParsePrototype() { 267 | if (CurTok != tok_identifier) 268 | return LogErrorP("Expected function name in prototype"); 269 | 270 | std::string FnName = IdentifierStr; 271 | getNextToken(); 272 | 273 | if (CurTok != '(') 274 | return LogErrorP("Expected '(' in prototype"); 275 | 276 | std::vector ArgNames; 277 | while (getNextToken() == tok_identifier) 278 | ArgNames.push_back(IdentifierStr); 279 | if (CurTok != ')') 280 | return LogErrorP("Expected ')' in prototype"); 281 | 282 | getNextToken(); 283 | 284 | return std::make_unique(FnName, std::move(ArgNames)); 285 | } 286 | 287 | static std::unique_ptr ParseDefinition() { 288 | getNextToken(); 289 | auto Proto = ParsePrototype(); 290 | if (!Proto) 291 | return nullptr; 292 | 293 | if (auto E = ParseExpression()) 294 | return std::make_unique(std::move(Proto), std::move(E)); 295 | return nullptr; 296 | } 297 | 298 | static std::unique_ptr ParseTopLevelExpr() { 299 | if (auto E = ParseExpression()) { 300 | auto Proto = std::make_unique("__anon_expr", 301 | std::vector()); 302 | return std::make_unique(std::move(Proto), std::move(E)); 303 | } 304 | return nullptr; 305 | } 306 | 307 | static std::unique_ptr ParseExtern() { 308 | getNextToken(); 309 | return ParsePrototype(); 310 | } 311 | 312 | static void HandleDefinition() { 313 | if (ParseDefinition()) { 314 | fprintf(stderr, "Parsed a function definition.\n"); 315 | } else { 316 | getNextToken(); 317 | } 318 | } 319 | 320 | static void HandleExtern() { 321 | if (ParseExtern()) { 322 | fprintf(stderr, "Parsed an extern\n"); 323 | } else { 324 | getNextToken(); 325 | } 326 | } 327 | 328 | static void HanldeTopLevelExpression() { 329 | if (ParseTopLevelExpr()) { 330 | fprintf(stderr, "Parsed a top-level expr.\n"); 331 | } else { 332 | getNextToken(); 333 | } 334 | } 335 | 336 | static void MainLoop() { 337 | while (true) { 338 | fprintf(stderr, "ready> "); 339 | switch (CurTok) { 340 | case tok_eof: 341 | return; 342 | case ';': 343 | getNextToken(); 344 | break; 345 | case tok_def: 346 | HandleDefinition(); 347 | break; 348 | case tok_extern: 349 | HandleExtern(); 350 | break; 351 | default: 352 | HanldeTopLevelExpression(); 353 | break; 354 | } 355 | } 356 | } 357 | 358 | int main() { 359 | 360 | BinopPrecedence['<'] = 10; 361 | BinopPrecedence['+'] = 20; 362 | BinopPrecedence['-'] = 20; 363 | BinopPrecedence['*'] = 40; 364 | 365 | fprintf(stderr, "ready> "); 366 | getNextToken(); 367 | 368 | MainLoop(); 369 | 370 | return 0; 371 | } 372 | -------------------------------------------------------------------------------- /doc/Kaleidoscope09.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:添加调试信息 2 | 3 | ## 第九章引言 4 | 5 | 在第1章到第8章中,我们已经用函数和变量构建了一种不错的小型编程语言。但是,如果出现问题怎么办,您如何调试您的程序呢? 6 | 7 | 源代码级别调试使用格式化数据来帮助调试器将二进制代码和计算机状态转换回程序员编写的源代码。在LLVM中,我们通常使用称为[DWARF](http://dwarfstd.org)格式。DWARF是一种表示类型、源代码位置和变量位置的紧凑编码。 8 | 9 | 本章的简短总结是,我们将介绍为支持调试信息而必须添加到编程语言中的各种内容,以及如何将其转换为DWARF。 10 | 11 | > 警告:目前我们不能通过JIT进行调试,因此我们需要将我们的程序编译成一些小而独立的东西。作为这项工作的一部分,我们将对语言的运行和程序的编译方式进行一些修改。这意味着我们将有一个源文件,其中包含一个用Kaleidoscope而不是交互式JIT编写的简单程序。它确实涉及一个限制,即我们一次只能有一个“顶层”命令,以减少必要的更改次数。 12 | 13 | 下面是我们将要编译的示例程序: 14 | 15 | ```python 16 | def fib(x) 17 | if x < 3 then 18 | 1 19 | else 20 | fib(x-1)+fib(x-2); 21 | 22 | fib(10) 23 | ``` 24 | 25 | ## 为什么这是一个很难解决的问题? 26 | 27 | 由于几个不同的原因,调试信息是一个棘手的问题-主要集中在优化的代码上。首先,优化使得保持源代码位置更加困难。在LLVM IR中,我们在指令上保留每个IR级别指令的原始源位置。优化passes应该保留新创建的指令的源位置,但合并的指令只保留一个位置-这可能会导致在单步执行优化程序时原地跳转。其次,优化可以通过优化、与其他变量共享内存或难以跟踪的方式移动变量。出于本教程的目的,我们将避免优化(正如您将在接下来的补丁程序中看到的那样)。 28 | 29 | ## 提前编译模式 30 | 31 | 为了只强调将调试信息添加到源语言的各个方面,而不需要担心JIT调试的复杂性,我们将对Kaleidoscope进行一些更改,以支持将前端发出的IR编译成可以执行、调试和查看结果的简单独立程序。 32 | 33 | 首先,我们将包含顶层语句的匿名函数设置为“main”: 34 | 35 | ```udiff 36 | - auto Proto = std::make_unique("", std::vector()); 37 | + auto Proto = std::make_unique("main", std::vector()); 38 | ``` 39 | 40 | 只是简单地给它起了个名字。 41 | 42 | 然后,我们将删除任何存在的命令行代码: 43 | 44 | ```udiff 45 | @@ -1129,7 +1129,6 @@ static void HandleTopLevelExpression() { 46 | /// top ::= definition | external | expression | ';' 47 | static void MainLoop() { 48 | while (1) { 49 | - fprintf(stderr, "ready> "); 50 | switch (CurTok) { 51 | case tok_eof: 52 | return; 53 | @@ -1184,7 +1183,6 @@ int main() { 54 | BinopPrecedence['*'] = 40; // highest. 55 | 56 | // Prime the first token. 57 | - fprintf(stderr, "ready> "); 58 | getNextToken(); 59 | ``` 60 | 61 | 最后,我们将禁用所有优化过程和JIT,以便在我们完成解析和生成代码后唯一发生的事情是LLVM IR转到标准错误流输出: 62 | 63 | ```udiff 64 | @@ -1108,17 +1108,8 @@ static void HandleExtern() { 65 | static void HandleTopLevelExpression() { 66 | // Evaluate a top-level expression into an anonymous function. 67 | if (auto FnAST = ParseTopLevelExpr()) { 68 | - if (auto *FnIR = FnAST->codegen()) { 69 | - // We're just doing this to make sure it executes. 70 | - TheExecutionEngine->finalizeObject(); 71 | - // JIT the function, returning a function pointer. 72 | - void *FPtr = TheExecutionEngine->getPointerToFunction(FnIR); 73 | - 74 | - // Cast it to the right type (takes no arguments, returns a double) so we 75 | - // can call it as a native function. 76 | - double (*FP)() = (double (*)())(intptr_t)FPtr; 77 | - // Ignore the return value for this. 78 | - (void)FP; 79 | + if (!F->codegen()) { 80 | + fprintf(stderr, "Error generating code for top level expr"); 81 | } 82 | } else { 83 | // Skip token for error recovery. 84 | @@ -1439,11 +1459,11 @@ int main() { 85 | // target lays out data structures. 86 | TheModule->setDataLayout(TheExecutionEngine->getDataLayout()); 87 | OurFPM.add(new DataLayoutPass()); 88 | +#if 0 89 | OurFPM.add(createBasicAliasAnalysisPass()); 90 | // Promote allocas to registers. 91 | OurFPM.add(createPromoteMemoryToRegisterPass()); 92 | @@ -1218,7 +1210,7 @@ int main() { 93 | OurFPM.add(createGVNPass()); 94 | // Simplify the control flow graph (deleting unreachable blocks, etc). 95 | OurFPM.add(createCFGSimplificationPass()); 96 | - 97 | + #endif 98 | OurFPM.doInitialization(); 99 | 100 | // Set the global so the code gen can use this. 101 | ``` 102 | 103 | 这组相对较小的更改使我们可以通过以下命令行将我们的一段Kaleidoscope语言编译成可执行程序: 104 | 105 | ```bash 106 | Kaleidoscope-Ch9 < fib.ks | & clang -x ir - 107 | ``` 108 | 109 | 这将在当前工作目录中提供a.out/a.exe。 110 | 111 | ## 编译单元 112 | 113 | DWARF中代码段的顶层容器是编译单元。它包含单个翻译单元的类型和功能数据(读取:一个源代码文件)。因此,我们需要做的第一件事是为fier.ks文件构建一个编译单元。 114 | 115 | ## DWARF发射设置 116 | 117 | 与`IRBuilder`类类似,我们有一个[DIBuilder](https://llvm.org/doxygen/classllvm_1_1DIBuilder.html)类,它帮助构建LLVMIR文件的调试元数据。与`IRBuilder`和LLVM IR 1:1对应,但名称更好听。使用它确实需要您比熟悉`IRBuilder`和`Instruction`名称时更熟悉Dwarf术语,但是如果您通读[Metadata Format](https://llvm.org/docs/SourceLevelDebugging.html)]上的通用文档,应该会更清楚一些。我们将使用这个类来构造我们所有的IR级别描述。它的构造需要一个模块,所以我们需要在构造模块后不久构造它。为了使它更易于使用,我们将其保留为全局静态变量。 118 | 119 | 接下来,我们将创建一个小容器来缓存一些频繁使用的数据。第一个容器将是我们的编译单元,但是我们也将为我们的每种类型编写一些代码,因为我们不必担心多个类型的表达式: 120 | 121 | ```c++ 122 | static DIBuilder *DBuilder; 123 | 124 | struct DebugInfo { 125 | DICompileUnit *TheCU; 126 | DIType *DblTy; 127 | 128 | DIType *getDoubleTy(); 129 | } KSDbgInfo; 130 | 131 | DIType *DebugInfo::getDoubleTy() { 132 | if (DblTy) 133 | return DblTy; 134 | 135 | DblTy = DBuilder->createBasicType("double", 64, dwarf::DW_ATE_float); 136 | return DblTy; 137 | } 138 | ``` 139 | 140 | 然后在稍后的“main`”中,当我们构建我们的模块时: 141 | 142 | ```c++ 143 | DBuilder = new DIBuilder(*TheModule); 144 | 145 | KSDbgInfo.TheCU = DBuilder->createCompileUnit( 146 | dwarf::DW_LANG_C, DBuilder->createFile("fib.ks", "."), 147 | "Kaleidoscope Compiler", 0, "", 0); 148 | ``` 149 | 150 | 这里有几件事需要注意。首先,当我们为名为Kaleidoscope的语言生成编译单元时,我们使用了C语言中的常量,这是因为调试器不一定理解它无法识别的语言的调用约定或缺省ABI,并且我们在LLVM代码生成中遵循C ABI,所以它是最接近准确的。这确保了我们可以实际从调试器调用函数并执行它们。其次,您将在对`createCompileUnit`的调用中看到“fib.ks”。这是默认的硬编码值,因为我们使用shell重定向将源代码放入Kaleidoscope编译器。在通常的前端,您会有一个输入文件名,它会放在那里。 151 | 152 | 通过DIBuilder发出调试信息的最后一件事是,我们需要“确定”调试信息。原因是DIBuilder的底层API的一部分,但请确保在Main的末尾,导出模块之前执行此操作: 153 | 154 | ```c++ 155 | DBuilder->finalize(); 156 | ``` 157 | 158 | ## 函数 159 | 160 | 现在我们有了`Compile Unit`和源位置,我们可以将函数定义添加到调试信息中。因此,在`PrototypeAST::codegen()`中,我们添加了几行代码来描述子程序的上下文,在本例中为“File”,以及函数本身的实际定义。 161 | 162 | 所以上下文是这样的: 163 | 164 | ```c++ 165 | DIFile *Unit = DBuilder->createFile(KSDbgInfo.TheCU.getFilename(), 166 | KSDbgInfo.TheCU.getDirectory()); 167 | ``` 168 | 169 | 给我们一个DIFile,并向我们上面创建的`Compile Unit`询问我们当前所在的目录和文件名。现在,我们使用一些值为0的源位置(因为我们的AST当前没有源位置信息),并构造我们的函数定义: 170 | 171 | ```c++ 172 | DIScope *FContext = Unit; 173 | unsigned LineNo = 0; 174 | unsigned ScopeLine = 0; 175 | DISubprogram *SP = DBuilder->createFunction( 176 | FContext, P.getName(), StringRef(), Unit, LineNo, 177 | CreateFunctionType(TheFunction->arg_size(), Unit), 178 | false /* internal linkage */, true /* definition */, ScopeLine, 179 | DINode::FlagPrototyped, false); 180 | TheFunction->setSubprogram(SP); 181 | ``` 182 | 183 | 现在我们有了一个DISubProgram,它包含对函数的所有元数据的引用。 184 | 185 | ## 源位置 186 | 187 | 调试信息最重要的是准确的源代码位置-这使得您可以将源代码映射回原来的位置。但是我们有一个问题,Kaleidoscope在词法分析器或解析器中确实没有任何源位置信息,所以我们需要添加它。 188 | 189 | ```c++ 190 | struct SourceLocation { 191 | int Line; 192 | int Col; 193 | }; 194 | static SourceLocation CurLoc; 195 | static SourceLocation LexLoc = {1, 0}; 196 | 197 | static int advance() { 198 | int LastChar = getchar(); 199 | 200 | if (LastChar == '\n' || LastChar == '\r') { 201 | LexLoc.Line++; 202 | LexLoc.Col = 0; 203 | } else 204 | LexLoc.Col++; 205 | return LastChar; 206 | } 207 | ``` 208 | 209 | 在这组代码中,我们添加了一些关于如何跟踪“源文件”的行和列的功能。当我们对每个令牌进行lex时,我们将当前的“lexical location”设置为令牌开头的分类行和列。为此,我们使用跟踪信息的新的`Advance()`覆盖了之前对`getchar()`的所有调用,然后我们向所有AST类添加了一个源位置: 210 | 211 | ```c++ 212 | class ExprAST { 213 | SourceLocation Loc; 214 | 215 | public: 216 | ExprAST(SourceLocation Loc = CurLoc) : Loc(Loc) {} 217 | virtual ~ExprAST() {} 218 | virtual Value* codegen() = 0; 219 | int getLine() const { return Loc.Line; } 220 | int getCol() const { return Loc.Col; } 221 | virtual raw_ostream &dump(raw_ostream &out, int ind) { 222 | return out << ':' << getLine() << ':' << getCol() << '\n'; 223 | } 224 | ``` 225 | 226 | 我们在创建新表达式时会传递这些信息: 227 | 228 | ```c++ 229 | LHS = std::make_unique(BinLoc, BinOp, std::move(LHS), 230 | std::move(RHS)); 231 | ``` 232 | 233 | 为我们提供每个表达式和变量的位置。 234 | 235 | 为了确保每条指令都能获得正确的源位置信息,每当我们在一个新的源位置时,我们都必须告诉`Builder`。为此,我们使用了一个小的辅助函数: 236 | 237 | ```c++ 238 | void DebugInfo::emitLocation(ExprAST *AST) { 239 | DIScope *Scope; 240 | if (LexicalBlocks.empty()) 241 | Scope = TheCU; 242 | else 243 | Scope = LexicalBlocks.back(); 244 | Builder.SetCurrentDebugLocation( 245 | DILocation::get(Scope->getContext(), AST->getLine(), AST->getCol(), Scope)); 246 | } 247 | ``` 248 | 249 | 这既告诉主`IRBuilder‘我们所在的位置,也告诉我们所在的作用域。作用域可以是编译单元级别的,也可以是最接近的封闭词法block,比如当前函数。为了表示这一点,我们创建了一个作用域堆栈: 250 | 251 | ```c++ 252 | std::vector LexicalBlocks; 253 | ``` 254 | 255 | 并在开始为每个函数生成代码时将作用域(函数)推到堆栈的顶部: 256 | 257 | ```c++ 258 | KSDbgInfo.LexicalBlocks.push_back(SP); 259 | ``` 260 | 261 | 此外,我们不能忘记在函数的代码生成结束时将作用域从作用域堆栈中弹出: 262 | 263 | ```c++ 264 | // Pop off the lexical block for the function since we added it 265 | // unconditionally. 266 | KSDbgInfo.LexicalBlocks.pop_back(); 267 | ``` 268 | 269 | 然后,我们确保在每次开始为新AST对象生成代码时发出位置: 270 | 271 | ```c++ 272 | KSDbgInfo.emitLocation(this); 273 | ``` 274 | 275 | ## 变量 276 | 277 | 现在我们有了函数,我们需要能够打印出范围内的变量。让我们设置我们的函数参数,这样我们就可以进行适当的回溯,看看我们的函数是如何被调用的。这不是很多代码,我们通常在`FunctionAST::codegen`中创建参数allocas时处理它。 278 | 279 | ```c++ 280 | // Record the function arguments in the NamedValues map. 281 | NamedValues.clear(); 282 | unsigned ArgIdx = 0; 283 | for (auto &Arg : TheFunction->args()) { 284 | // Create an alloca for this variable. 285 | AllocaInst *Alloca = CreateEntryBlockAlloca(TheFunction, Arg.getName()); 286 | 287 | // Create a debug descriptor for the variable. 288 | DILocalVariable *D = DBuilder->createParameterVariable( 289 | SP, Arg.getName(), ++ArgIdx, Unit, LineNo, KSDbgInfo.getDoubleTy(), 290 | true); 291 | 292 | DBuilder->insertDeclare(Alloca, D, DBuilder->createExpression(), 293 | DILocation::get(SP->getContext(), LineNo, 0, SP), 294 | Builder.GetInsertBlock()); 295 | 296 | // Store the initial value into the alloca. 297 | Builder.CreateStore(&Arg, Alloca); 298 | 299 | // Add arguments to variable symbol table. 300 | NamedValues[Arg.getName()] = Alloca; 301 | } 302 | ``` 303 | 304 | 在这里,我们首先创建变量,为其提供作用域(`SP`)、名称、源位置、类型,并且由于它是参数,因此还提供参数索引。接下来,我们创建一个`lvm.dbg.declare`调用,以在IR级别指示我们在alloca中有一个变量(并且它给出变量的起始位置),并在声明上设置作用域开始的源位置。 305 | 306 | 在这一点上需要注意的一件有趣的事情是,各种调试器都有基于过去如何为它们生成代码和调试信息的假设。在这种情况下,我们需要做一些修改,以避免为函数序言生成行信息,以便调试器知道在设置断点时跳过这些指令。所以在`FunctionAST::CodeGen`中,我们再增加几行: 307 | 308 | ```c++ 309 | // Unset the location for the prologue emission (leading instructions with no 310 | // location in a function are considered part of the prologue and the debugger 311 | // will run past them when breaking on a function) 312 | KSDbgInfo.emitLocation(nullptr); 313 | ``` 314 | 315 | 然后在我们实际开始为函数体生成代码时发出一个新位置: 316 | 317 | ```c++ 318 | KSDbgInfo.emitLocation(Body.get()); 319 | ``` 320 | 321 | 这样,我们就有了足够的调试信息,可以在函数中设置断点、打印参数变量和调用函数。对于仅仅几行简单的代码来说还不错! 322 | 323 | ## 完整代码列表 324 | 325 | 下面是我们运行示例的完整代码清单,并使用调试信息进行了增强。要构建此示例,请使用: 326 | 327 | ```bash 328 | # Compile 329 | clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core orcjit native` -O3 -o toy 330 | # Run 331 | ./toy 332 | ``` 333 | 334 | 以下是代码: 335 | 336 | [下一步:结论和其他有用的LLVM花絮](zh-LangImpl10.html) 337 | -------------------------------------------------------------------------------- /doc/Kaleidoscope03.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:LLVM IR的代码生成 2 | 3 | ## 第三章绪论 4 | 5 | 本章介绍如何将第2章中构建的[抽象语法树](Kaleidoscope02.md)转换为LLVM IR。这将教您一些关于LLVM是如何做事情的知识,并演示它的易用性。与生成LLVM IR代码相比,构建词法分析器和解析器的工作要多得多. 6 | 7 | ## 代码生成设置 8 | 9 | 为了生成LLVM IR,我们需要一些简单的设置。首先,我们在每个AST类中定义虚拟代码生成(Codegen)方法: 10 | 11 | ```c++ 12 | /// ExprAST - Base class for all expression nodes. 13 | class ExprAST { 14 | public: 15 | virtual ~ExprAST() {} 16 | virtual Value *codegen() = 0; 17 | }; 18 | 19 | /// NumberExprAST - Expression class for numeric literals like "1.0". 20 | class NumberExprAST : public ExprAST { 21 | double Val; 22 | 23 | public: 24 | NumberExprAST(double Val) : Val(Val) {} 25 | virtual Value *codegen(); 26 | }; 27 | ... 28 | ``` 29 | 30 | codegen()方法表示为该AST节点产生IR以及它所依赖的所有内容,并且它们都返回一个LLVM值对象。Value是用来表示LLVM中的“[静态单赋值(SSA)](http://en.wikipedia.org/wiki/Static_single_assignment_form)寄存器”或“SSA值”的类。SSA值最明显的方面是,它们的值是在相关指令执行时计算的,并且直到(如果)指令重新执行时才会获得新值。换句话说,没有办法“更改”SSA值。欲了解更多信息,请阅读[静态单赋值](http://en.wikipedia.org/wiki/Static_single_assignment_form) - 一旦你去研究,这些概念就真的很自然了。 31 | 32 | 请注意,除了将虚方法添加到ExprAST类层次结构中,使用[访问者模式](http://en.wikipedia.org/wiki/Visitor_pattern)或其他方式对此进行建模也是有意义的。重申一下,本教程不会详述好的软件工程实践:就我们的目的而言,添加虚拟方法是最简单的。 33 | 34 | 我们需要的第二件事是“LogError”方法,就像我们用于解析器一样,它将用于报告在代码生成过程中发现的错误(例如,使用未声明的参数): 35 | 36 | ```c++ 37 | static LLVMContext TheContext; 38 | static IRBuilder<> Builder(TheContext); 39 | static std::unique_ptr TheModule; 40 | static std::map NamedValues; 41 | 42 | Value *LogErrorV(const char *Str) { 43 | LogError(Str); 44 | return nullptr; 45 | } 46 | ``` 47 | 48 | 静态变量将在代码生成期间使用。`TheContext`是一个不透明的对象,拥有大量的LLVM核心数据结构,比如类型表和常量值表。我们不需要详细了解它,我们只需要一个实例来传递给需要它的API。 49 | 50 | `Builder`对象是一个帮助对象,可以轻松生成LLVM指令。[IRBuilder](https://llvm.org/doxygen/IRBuilder_8h_source.html)类模板的实例跟踪当前插入指令的位置,并具有创建新指令的方法。 51 | 52 | `TheModule`是包含函数和全局变量的LLVM结构。在许多方面,它是LLVM IR用来包含代码的顶层结构。它将拥有我们生成的所有IR的内存,这就是codegen()方法返回raw Value\*而不是unique_ptr\的原因。 53 | 54 | `NamedValues`映射跟踪在当前作用域中定义了哪些值,以及它们的LLVM表示是什么。(换句话说,它是代码的符号表)。在这种形式的Kaleidoscope中,唯一可以引用的是函数参数。因此,在为函数主体生成代码时,函数参数将在此映射中。 55 | 56 | 有了这些基础知识后,我们就可以开始讨论如何为每个表达式生成代码了。请注意,这假设`Builder`已设置为生成代码*变成*什么(译者注:即生成目标代码类型,比如x86的汇编还是ARM汇编)。现在,我们假设这已经完成了,我们将只使用它来发出代码。 57 | 58 | ## 表达式代码生成 59 | 60 | 为表达式节点生成LLVM代码非常简单:所有四个表达式节点加上注释代码不到45行。首先,我们要做的是数字常量: 61 | 62 | ```c++ 63 | Value *NumberExprAST::codegen() { 64 | return ConstantFP::get(TheContext, APFloat(Val)); 65 | } 66 | ``` 67 | 68 | 在LLVM IR中,数值常量由`ConstantFP`类表示,该类在内部保存`APFloat`中的数值(`APFloat`可以保存任意精度的浮点常量)。这段代码基本上只是创建并返回一个`ConstantFP`。请注意,在LLVM IR中,所有常量都是唯一的,并且都是共享的。为此,API使用了“foo::get(\.)”习惯用法,而不是“new foo(..)”或“foo::create(..)”。 69 | 70 | ```c++ 71 | Value *VariableExprAST::codegen() { 72 | // Look this variable up in the function. 73 | Value *V = NamedValues[Name]; 74 | if (!V) 75 | LogErrorV("Unknown variable name"); 76 | return V; 77 | } 78 | ``` 79 | 80 | 使用LLVM引用变量也非常简单。在简单版本的Kaleidoscope中,我们假设变量已经在某个地方发出,并且它的值是可用的。实际上,`NamedValues`映射中唯一可以出现的值是函数参数。这段代码只是检查映射中是否有指定的名称(如果没有,则表示引用了一个未知变量)并返回该变量的值。在以后的章节中,我们将添加对符号表中的[循环指示变量(LOOP induction variables)](Kaleidoscope05.md)]和[本地变量(LOCAL variables)](Kaleidoscope07.md)的支持。 81 | 82 | ```c++ 83 | Value *BinaryExprAST::codegen() { 84 | Value *L = LHS->codegen(); 85 | Value *R = RHS->codegen(); 86 | if (!L || !R) 87 | return nullptr; 88 | 89 | switch (Op) { 90 | case '+': 91 | return Builder.CreateFAdd(L, R, "addtmp"); 92 | case '-': 93 | return Builder.CreateFSub(L, R, "subtmp"); 94 | case '*': 95 | return Builder.CreateFMul(L, R, "multmp"); 96 | case '<': 97 | L = Builder.CreateFCmpULT(L, R, "cmptmp"); 98 | // Convert bool 0/1 to double 0.0 or 1.0 99 | return Builder.CreateUIToFP(L, Type::getDoubleTy(TheContext), 100 | "booltmp"); 101 | default: 102 | return LogErrorV("invalid binary operator"); 103 | } 104 | } 105 | ``` 106 | 107 | 二元运算符开始变得更加有趣。这里的基本思想是,我们递归地发出表达式左侧的代码,然后是右侧的代码,然后计算二元表达式的结果。在这段代码中,我们简单地替换操作码以创建正确的LLVM指令。 108 | 109 | 在上面的示例中,LLVM构建器类开始显示其价值。IRBuilder知道插入新创建的指令的位置,您只需指定要创建的指令(例如,使用`CreateFAdd`)、要使用的操作数(这里是`L`和`R`),并可选择为生成的指令提供名称。 110 | 111 | LLVM的一个优点是名称只是一个提示。例如,如果上面的代码发出多个“addtmp”变量,LLVM将自动为每个变量提供一个递增的唯一数字后缀。指令的本地值名称纯粹是可选的,但它使读取IR转储变得容易得多。 112 | 113 | [LLVM instructions](https://llvm.org/docs/LangRef.html#instruction-reference)有严格的规则约束:例如,[Add instruction](https://llvm.org/docs/LangRef.html#add-instruction)的左运算符和右运算符必须具有相同的类型,并且Add的结果类型必须与操作数类型匹配。因为Kaleidoscope中的所有值都是双精度的,所以这使得加法、减法和乘法的代码非常简单。 114 | 115 | 另一方面,llvm指定[fcmp instruction](https://llvm.org/docs/LangRef.html#fcmp-instruction)总是返回‘i1’值(一位整数)。这样做的问题是Kaleidoscope希望该值是0.0或1.0。为了获得这些语义,我们将fcmp指令与[uitofp instruction](https://llvm.org/docs/LangRef.html#uitofp-to-instruction)组合在一起。此指令通过将输入视为无符号值,将其输入整数转换为浮点值。相反,如果我们使用[Sitofp instruction](https://llvm.org/docs/LangRef.html#sitofp-to-instruction),则根据输入值的不同,Kaleidoscope‘\<’运算符将返回0.0和-1.0。 116 | 117 | ```c++ 118 | Value *CallExprAST::codegen() { 119 | // Look up the name in the global module table. 120 | Function *CalleeF = TheModule->getFunction(Callee); 121 | if (!CalleeF) 122 | return LogErrorV("Unknown function referenced"); 123 | 124 | // If argument mismatch error. 125 | if (CalleeF->arg_size() != Args.size()) 126 | return LogErrorV("Incorrect # arguments passed"); 127 | 128 | std::vector ArgsV; 129 | for (unsigned i = 0, e = Args.size(); i != e; ++i) { 130 | ArgsV.push_back(Args[i]->codegen()); 131 | if (!ArgsV.back()) 132 | return nullptr; 133 | } 134 | 135 | return Builder.CreateCall(CalleeF, ArgsV, "calltmp"); 136 | } 137 | ``` 138 | 139 | 使用LLVM,函数调用的代码生成非常简单。上面的代码最初在LLVM模块的符号表中查找函数名。回想一下,LLVM模块是保存我们正在JIT的函数的容器。通过赋予每个函数与用户指定的名称相同的名称,我们可以使用LLVM符号表为我们解析函数名。 140 | 141 | 一旦我们有了要调用的函数,我们就递归地对要传入的每个参数进行编码,并创建一个llvm[调用instruction](https://llvm.org/docs/LangRef.html#call-instruction).请注意,默认情况下,LLVM使用原生C调用约定,允许这些调用还可以调用标准库函数(如“sin”和“cos”),而不需要额外的工作。 142 | 143 | 到目前为止,我们对Kaleidoscope中的四个基本表达式的处理到此结束。请随意进去,再加一些。例如,通过浏览[LLVM Language Reference](https://llvm.org/docs/LangRef.html),您会发现其他几个有趣的指令,它们非常容易插入到我们的基本框架中。 144 | 145 | ## 函数代码生成 146 | 147 | 原型和函数的代码生成必须处理许多细节,这些细节使它们的代码不如表达式代码生成美观,但允许我们说明一些重要的点。首先,让我们讨论一下原型的代码生成:它们既用于函数体,也用于外部函数声明。代码如下: 148 | 149 | ```c++ 150 | Function *PrototypeAST::codegen() { 151 | // Make the function type: double(double,double) etc. 152 | std::vector Doubles(Args.size(), 153 | Type::getDoubleTy(TheContext)); 154 | FunctionType *FT = 155 | FunctionType::get(Type::getDoubleTy(TheContext), Doubles, false); 156 | 157 | Function *F = 158 | Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get()); 159 | ``` 160 | 161 | 此代码将大量功能打包到几行中。首先请注意,此函数返回”function\*”,而不是”value\*”。因为”Prototype”实际上谈论的是函数的外部接口(而不是表达式计算的值),所以当codegen‘d时,它返回与之对应的LLVM函数是有意义的。 162 | 163 | 对`FunctionType::get`的调用创建了应该用于给定原型的`FunctionType`。因为Kaleidoscope中的所有函数参数都是DOUBLE类型,所以第一行创建了一个”N”LLVM DOUBLE类型的向量。然后使用`Functiontype::get`方法创建一个函数类型,该函数类型以”N”双精度值作为参数,返回一个双精度值作为结果,并且不是vararg(false参数表示这一点)。请注意,LLVM中的类型与常量一样是唯一的,因此您不会“新建”类型,而是“获取”它。 164 | 165 | 上面的最后一行实际上创建了与原型相对应的IR函数。这指示要使用的类型、链接和名称,以及要插入的模块。”[外部链接](https://llvm.org/docs/LangRef.html#linkage)”表示函数可以在当前模块外部定义和/或可以由模块外部的函数调用。传入的名称是用户指定的名称:由于指定了”`TheModule`”,所以该名称注册在”`TheModule`”的符号表中。 166 | 167 | ```c++ 168 | // Set names for all arguments. 169 | unsigned Idx = 0; 170 | for (auto &Arg : F->args()) 171 | Arg.setName(Args[Idx++]); 172 | 173 | return F; 174 | ``` 175 | 176 | 最后,我们根据原型中给出的名称设置每个函数参数的名称。这一步并不是严格必要的,但是保持名称的一致性会使IR更具可读性,并且允许后续代码直接引用它们的名称的参数,而不必在原型AST中查找它们。 177 | 178 | 此时,我们有了一个没有函数体的函数原型。这就是LLVM IR表示函数声明的方式。对于Kaleidoscope中的外部(extern)语句,这就是我们需要做的。然而,对于函数定义,我们需要编码生成并附加一个函数体。 179 | 180 | ```c++ 181 | Function *FunctionAST::codegen() { 182 | // First, check for an existing function from a previous 'extern' declaration. 183 | Function *TheFunction = TheModule->getFunction(Proto->getName()); 184 | 185 | if (!TheFunction) 186 | TheFunction = Proto->codegen(); 187 | 188 | if (!TheFunction) 189 | return nullptr; 190 | 191 | if (!TheFunction->empty()) 192 | return (Function*)LogErrorV("Function cannot be redefined."); 193 | ``` 194 | 195 | 对于函数定义,我们首先在模块的符号表中搜索此函数的现有版本(如果已经使用‘extern’语句创建了一个版本)。如果Module::getFunction返回NULL,则不存在以前的版本,因此我们将从原型中编码生成一个。在任何一种情况下,我们都希望在开始之前断言函数为空(即还没有主体)。 196 | 197 | ```c++ 198 | // Create a new basic block to start insertion into. 199 | BasicBlock *BB = BasicBlock::Create(TheContext, "entry", TheFunction); 200 | Builder.SetInsertPoint(BB); 201 | 202 | // Record the function arguments in the NamedValues map. 203 | NamedValues.clear(); 204 | for (auto &Arg : TheFunction->args()) 205 | NamedValues[Arg.getName()] = &Arg; 206 | ``` 207 | 208 | 现在我们到了设置`Builder`的地方。第一行创建一个新的[basic block](http://en.wikipedia.org/wiki/Basic_block)”插入到`TheFunction`中。然后第二行告诉构建器,应该在新的`Basic block`的末尾插入新的指令。LLVM中的基本块是定义[控制流Graph](http://en.wikipedia.org/wiki/Control_flow_graph)的函数的重要部分。因为我们没有任何控制流,所以我们的函数此时将只包含一个block。我们将在[第5章](Kaleidoscope05.md)中解决这个问题:)。 209 | 210 | 接下来,我们将函数参数添加到NamedValues映射中(在其清除之后),以便`VariableExprAST`节点可以访问它们。 211 | 212 | ```c++ 213 | if (Value *RetVal = Body->codegen()) { 214 | // Finish off the function. 215 | Builder.CreateRet(RetVal); 216 | 217 | // Validate the generated code, checking for consistency. 218 | verifyFunction(*TheFunction); 219 | 220 | return TheFunction; 221 | } 222 | ``` 223 | 224 | 一旦设置了插入点并填充了NamedValues映射,我们就会为函数的根表达式调用`codegen()`方法。如果没有发生错误,这将发出代码来计算表达式添加到entry block,并返回计算出的值。假设没有错误,我们会创建一个完成该功能的llvm [ret instruction](https://llvm.org/docs/LangRef.html#ret-instruction)。函数构建完成后,调用LLVM提供的`verifyFunction`。此函数对生成的代码执行各种一致性检查,以确定我们的编译器是否一切正常。使用它很重要:它可以捕获很多错误。一旦函数完成并经过验证,我们就会返回它。 225 | 226 | ```c++ 227 | // Error reading body, remove function. 228 | TheFunction->eraseFromParent(); 229 | return nullptr; 230 | } 231 | ``` 232 | 233 | 这里剩下的唯一部分就是错误情况的处理。为简单起见,我们只需使用`eraseFromParent`方法删除生成的函数即可处理此问题。这允许用户重新定义他们以前错误键入的函数:如果我们不删除它,它将与函数体一起存在于符号表中,防止将来重新定义。 234 | 235 | 不过,此代码确实有一个缺陷:如果`FunctionAST::codegen()`方法找到一个现有的IR函数,它不会根据定义自己的原型验证其签名。这意味着较早的‘extern’声明将优先于函数定义的签名,这可能会导致codegen失败,例如,如果函数参数命名不同。有很多方法可以修复此缺陷,看看您能想到什么!下面是一个测试用例: 236 | 237 | ``` 238 | extern foo(a); # ok, defines foo. 239 | def foo(b) b; # Error: Unknown variable name. (decl using 'a' takes precedence). 240 | ``` 241 | 242 | ## 驱动程序更改和结束思路 243 | 244 | 目前,LLVM的代码生成并没有给我们带来多少好处,除了我们可以查看漂亮的IR调用之外。示例代码将codegen的调用插入到”`HandleDefinition`”、”`HandleExtern`”等函数中,然后转储LLVM IR。这为查看简单函数的LLVM IR提供了一个很好的方法。例如: 245 | ``` 246 | ready> 4+5; 247 | Read top-level expression: 248 | define double @0() { 249 | entry: 250 | ret double 9.000000e+00 251 | } 252 | ``` 253 | 254 | 请注意解析器如何为我们将顶层表达式转换为匿名函数。当我们在下一章中添加[JIT support](Kaleidoscope04.md)]时,这将非常方便。还要注意的是,代码是按字面意思转录的,除了IRBuilder执行的简单常量折叠外,没有执行任何优化。我们将在下一章中[显式添加optimizations](Kaleidoscope04.md)。 255 | 256 | ``` 257 | ready> def foo(a b) a*a + 2*a*b + b*b; 258 | Read function definition: 259 | define double @foo(double %a, double %b) { 260 | entry: 261 | %multmp = fmul double %a, %a 262 | %multmp1 = fmul double 2.000000e+00, %a 263 | %multmp2 = fmul double %multmp1, %b 264 | %addtmp = fadd double %multmp, %multmp2 265 | %multmp3 = fmul double %b, %b 266 | %addtmp4 = fadd double %addtmp, %multmp3 267 | ret double %addtmp4 268 | } 269 | ``` 270 | 271 | 这显示了一些简单的算术运算。请注意,它与我们用来创建指令的LLVM构建器调用有惊人的相似之处。 272 | 273 | ``` 274 | ready> def bar(a) foo(a, 4.0) + bar(31337); 275 | Read function definition: 276 | define double @bar(double %a) { 277 | entry: 278 | %calltmp = call double @foo(double %a, double 4.000000e+00) 279 | %calltmp1 = call double @bar(double 3.133700e+04) 280 | %addtmp = fadd double %calltmp, %calltmp1 281 | ret double %addtmp 282 | } 283 | ``` 284 | 285 | 这显示了一些函数调用。请注意,如果调用此函数,将需要很长的执行时间。在将来,我们将添加条件控制流以使递归真正有用:)。 286 | 287 | ``` 288 | ready> extern cos(x); 289 | Read extern: 290 | declare double @cos(double) 291 | 292 | ready> cos(1.234); 293 | Read top-level expression: 294 | define double @1() { 295 | entry: 296 | %calltmp = call double @cos(double 1.234000e+00) 297 | ret double %calltmp 298 | } 299 | ``` 300 | 301 | 这显示了一个extern函数libm”cos”函数,以及对它的调用。 302 | 303 | ``` 304 | ready> ^D 305 | ; ModuleID = 'my cool jit' 306 | 307 | define double @0() { 308 | entry: 309 | %addtmp = fadd double 4.000000e+00, 5.000000e+00 310 | ret double %addtmp 311 | } 312 | 313 | define double @foo(double %a, double %b) { 314 | entry: 315 | %multmp = fmul double %a, %a 316 | %multmp1 = fmul double 2.000000e+00, %a 317 | %multmp2 = fmul double %multmp1, %b 318 | %addtmp = fadd double %multmp, %multmp2 319 | %multmp3 = fmul double %b, %b 320 | %addtmp4 = fadd double %addtmp, %multmp3 321 | ret double %addtmp4 322 | } 323 | 324 | define double @bar(double %a) { 325 | entry: 326 | %calltmp = call double @foo(double %a, double 4.000000e+00) 327 | %calltmp1 = call double @bar(double 3.133700e+04) 328 | %addtmp = fadd double %calltmp, %calltmp1 329 | ret double %addtmp 330 | } 331 | 332 | declare double @cos(double) 333 | 334 | define double @1() { 335 | entry: 336 | %calltmp = call double @cos(double 1.234000e+00) 337 | ret double %calltmp 338 | } 339 | ``` 340 | 341 | 当您退出当前演示(在Linux上通过CTRL+D发送EOF,在Windows上通过CTRL+Z并回车)时,它会转储生成的整个模块的IR。在这里,您可以看到所有函数相互引用的整体情况。 342 | 343 | 这结束了Kaleidoscope教程的第三章。接下来,我们将描述如何[添加JIT代码生成和优化器支持](Kaleidoscope04.md),这样我们就可以真正开始运行代码了! 344 | 345 | 346 | [下一步:增加JIT和优化器支持](Kaleidoscope04.md) 347 | 348 | 349 | ## 后记:心得体会 350 | 1. 静态单赋值:https://blog.csdn.net/qq_38876114/article/details/111461727 351 | 2. llvm::LLVMContext使用; 352 | 3. llvm::Module使用; 353 | 4. llvm::IRBuilder使用; -------------------------------------------------------------------------------- /src/ch3_genIR.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by m0dulo on 2022/12/02. 3 | // 4 | 5 | #include "llvm/ADT/APFloat.h" 6 | #include "llvm/ADT/STLExtras.h" 7 | #include "llvm/IR/BasicBlock.h" 8 | #include "llvm/IR/Constants.h" 9 | #include "llvm/IR/DerivedTypes.h" 10 | #include "llvm/IR/Function.h" 11 | #include "llvm/IR/IRBuilder.h" 12 | #include "llvm/IR/LLVMContext.h" 13 | #include "llvm/IR/Module.h" 14 | #include "llvm/IR/Type.h" 15 | #include "llvm/IR/Verifier.h" 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace llvm; 27 | 28 | enum Token { 29 | tok_eof = -1, 30 | 31 | tok_def = -2, 32 | tok_extern = -3, 33 | 34 | tok_identifier = -4, 35 | tok_number = -5 36 | }; 37 | 38 | static std::string IdentifierStr; 39 | static double NumVal; 40 | 41 | static int gettok() { 42 | static int LastChar = ' '; 43 | 44 | while (isspace(LastChar)) 45 | LastChar = getchar(); 46 | 47 | if (isalpha(LastChar)) { 48 | IdentifierStr = LastChar; 49 | while (isalnum(LastChar = getchar())) 50 | IdentifierStr += LastChar; 51 | 52 | if (IdentifierStr == "def") 53 | return tok_def; 54 | if (IdentifierStr == "extern") 55 | return tok_extern; 56 | return tok_identifier; 57 | } 58 | 59 | if (isdigit(LastChar) || LastChar == '.') { 60 | std::string NumStr; 61 | do { 62 | NumStr += LastChar; 63 | LastChar = getchar(); 64 | } while (isdigit(LastChar) || LastChar == '.'); 65 | 66 | NumVal = strtod(NumStr.c_str(), nullptr); 67 | return tok_number; 68 | } 69 | 70 | if (LastChar == '#') { 71 | do 72 | LastChar = getchar(); 73 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 74 | 75 | if (LastChar != EOF) 76 | return gettok(); 77 | } 78 | 79 | if (LastChar == EOF) 80 | return tok_eof; 81 | 82 | int ThisChar = LastChar; 83 | LastChar = getchar(); 84 | return ThisChar; 85 | } 86 | 87 | namespace { 88 | 89 | class ExprAST { 90 | public: 91 | virtual ~ExprAST() = default; 92 | virtual Value *codegen() = 0; 93 | }; 94 | 95 | class NumberExprAST : public ExprAST { 96 | double Val; 97 | 98 | public: 99 | NumberExprAST(double Val) : Val(Val) {} 100 | Value *codegen() override; 101 | }; 102 | 103 | class VariableExprAST : public ExprAST { 104 | std::string Name; 105 | 106 | public: 107 | VariableExprAST(const std::string &Name) : Name(Name) {} 108 | Value *codegen() override; 109 | }; 110 | 111 | class BinaryExprAST : public ExprAST { 112 | char Op; 113 | std::unique_ptr LHS, RHS; 114 | 115 | public: 116 | BinaryExprAST(char Op, std::unique_ptr LHS, 117 | std::unique_ptr RHS) 118 | : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 119 | Value *codegen() override; 120 | }; 121 | 122 | class CallExprAST : public ExprAST { 123 | std::string Callee; 124 | std::vector> Args; 125 | 126 | public: 127 | CallExprAST(const std::string &Callee, 128 | std::vector> Args) 129 | : Callee(Callee), Args(std::move(Args)) {} 130 | Value *codegen() override; 131 | }; 132 | 133 | class PrototypeAST { 134 | std::string Name; 135 | std::vector Args; 136 | 137 | public: 138 | PrototypeAST(const std::string &Name, std::vector Args) 139 | : Name(Name), Args(std::move(Args)) {} 140 | 141 | const std::string &getName() const { return Name; } 142 | Function *codegen(); 143 | }; 144 | 145 | class FunctionAST { 146 | std::unique_ptr Proto; 147 | std::unique_ptr Body; 148 | 149 | public: 150 | FunctionAST(std::unique_ptr Proto, 151 | std::unique_ptr Body) 152 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 153 | Function *codegen(); 154 | }; 155 | 156 | } 157 | 158 | static int CurTok; 159 | static int getNextToken() { return CurTok = gettok(); } 160 | 161 | static std::map BinopPrecedence; 162 | 163 | static int GetTokPrecedence() { 164 | if (!isascii(CurTok)) 165 | return -1; 166 | 167 | int TokPrec = BinopPrecedence[CurTok]; 168 | if (TokPrec <= 0) 169 | return -1; 170 | return TokPrec; 171 | } 172 | 173 | std::unique_ptr LogError(const char *Str) { 174 | fprintf(stderr, "Error: %s\n", Str); 175 | return nullptr; 176 | } 177 | 178 | std::unique_ptr LogErrorP(const char *Str) { 179 | LogError(Str); 180 | return nullptr; 181 | } 182 | 183 | static std::unique_ptr ParseExpression(); 184 | 185 | static std::unique_ptr ParseNumberExpr() { 186 | auto Result = std::make_unique(NumVal); 187 | getNextToken(); 188 | return std::move(Result); 189 | } 190 | 191 | static std::unique_ptr ParseParenExpr() { 192 | getNextToken(); //( 193 | auto V = ParseExpression(); 194 | if (!V) 195 | return nullptr; 196 | 197 | if (CurTok != ')') 198 | return LogError("expected ')'"); 199 | getNextToken(); // ) 200 | return V; 201 | } 202 | 203 | static std::unique_ptr ParseIdentifierExpr() { 204 | std::string IdName = IdentifierStr; 205 | 206 | getNextToken(); 207 | 208 | if (CurTok != '(') 209 | return std::make_unique(IdName); 210 | 211 | getNextToken(); 212 | std::vector> Args; 213 | if (CurTok != ')') { 214 | while (true) { 215 | if (auto Arg = ParseExpression()) 216 | Args.push_back(std::move(Arg)); 217 | else 218 | return nullptr; 219 | 220 | if (CurTok == ')') 221 | break; 222 | 223 | if (CurTok != ',') 224 | return LogError("Expected ')' or ',' in argument list"); 225 | getNextToken(); 226 | } 227 | } 228 | 229 | getNextToken(); // ) 230 | 231 | return std::make_unique(IdName, std::move(Args)); 232 | } 233 | 234 | static std::unique_ptr ParsePrimary() { 235 | switch (CurTok) { 236 | default: 237 | return LogError("unknown token when expecting an expression"); 238 | case tok_identifier: 239 | return ParseIdentifierExpr(); 240 | case tok_number: 241 | return ParseNumberExpr(); 242 | case '(': 243 | return ParseParenExpr(); 244 | } 245 | } 246 | 247 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 248 | std::unique_ptr LHS) { 249 | while (true) { 250 | int TokPrec = GetTokPrecedence(); 251 | 252 | if (TokPrec < ExprPrec) 253 | return LHS; 254 | 255 | int BinOp = CurTok; 256 | getNextToken(); 257 | 258 | auto RHS = ParsePrimary(); 259 | if (!RHS) 260 | return nullptr; 261 | 262 | int NextPrec = GetTokPrecedence(); 263 | if (TokPrec < NextPrec) { 264 | RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS)); 265 | if (!RHS) 266 | return nullptr; 267 | } 268 | 269 | LHS = 270 | std::make_unique(BinOp, std::move(LHS), std::move(RHS)); 271 | } 272 | } 273 | 274 | static std::unique_ptr ParseExpression() { 275 | auto LHS = ParsePrimary(); 276 | if (!LHS) 277 | return nullptr; 278 | 279 | return ParseBinOpRHS(0, std::move(LHS)); 280 | } 281 | 282 | static std::unique_ptr ParsePrototype() { 283 | if (CurTok != tok_identifier) 284 | return LogErrorP("Expected function name in prototype"); 285 | 286 | std::string FnName = IdentifierStr; 287 | getNextToken(); 288 | 289 | if (CurTok != '(') 290 | return LogErrorP("Expected '(' in prototype"); 291 | 292 | std::vector ArgNames; 293 | while (getNextToken() == tok_identifier) 294 | ArgNames.push_back(IdentifierStr); 295 | if (CurTok != ')') 296 | return LogErrorP("Expected ')' in prototype"); 297 | 298 | getNextToken(); 299 | 300 | return std::make_unique(FnName, std::move(ArgNames)); 301 | } 302 | 303 | static std::unique_ptr ParseDefinition() { 304 | getNextToken(); 305 | auto Proto = ParsePrototype(); 306 | if (!Proto) 307 | return nullptr; 308 | 309 | if (auto E = ParseExpression()) 310 | return std::make_unique(std::move(Proto), std::move(E)); 311 | return nullptr; 312 | } 313 | 314 | static std::unique_ptr ParseTopLevelExpr() { 315 | if (auto E = ParseExpression()) { 316 | auto Proto = std::make_unique("__anon_expr", 317 | std::vector()); 318 | return std::make_unique(std::move(Proto), std::move(E)); 319 | } 320 | return nullptr; 321 | } 322 | 323 | static std::unique_ptr ParseExtern() { 324 | getNextToken(); 325 | return ParsePrototype(); 326 | } 327 | 328 | 329 | static std::unique_ptr TheContext; 330 | static std::unique_ptr TheModule; 331 | static std::unique_ptr> Builder; 332 | static std::map NamedValues; 333 | 334 | Value *LogErrorV(const char *Str) { 335 | LogError(Str); 336 | return nullptr; 337 | } 338 | 339 | Value *NumberExprAST::codegen() { 340 | return ConstantFP::get(*TheContext, APFloat(Val)); 341 | } 342 | 343 | Value *VariableExprAST::codegen() { 344 | // Look this variable up in the function. 345 | Value *V = NamedValues[Name]; 346 | if (!V) 347 | return LogErrorV("Unknown variable name"); 348 | return V; 349 | } 350 | 351 | Value *BinaryExprAST::codegen() { 352 | Value *L = LHS->codegen(); 353 | Value *R = RHS->codegen(); 354 | if (!L || !R) 355 | return nullptr; 356 | 357 | switch (Op) { 358 | case '+': 359 | return Builder->CreateFAdd(L, R, "addtmp"); 360 | case '-': 361 | return Builder->CreateFSub(L, R, "subtmp"); 362 | case '*': 363 | return Builder->CreateFMul(L, R, "multmp"); 364 | case '<': 365 | L = Builder->CreateFCmpULT(L, R, "cmptmp"); 366 | // Convert bool 0/1 to double 0.0 or 1.0 367 | return Builder->CreateUIToFP(L, Type::getDoubleTy(*TheContext), "booltmp"); 368 | default: 369 | return LogErrorV("invalid binary operator"); 370 | } 371 | } 372 | 373 | Value *CallExprAST::codegen() { 374 | 375 | Function *CalleeF = TheModule->getFunction(Callee); 376 | if (!CalleeF) 377 | return LogErrorV("Unknown function referenced"); 378 | 379 | if (CalleeF->arg_size() != Args.size()) 380 | return LogErrorV("Incorrect # arguments passed"); 381 | 382 | std::vector ArgsV; 383 | for (unsigned i = 0, e = Args.size(); i != e; ++i) { 384 | ArgsV.push_back(Args[i]->codegen()); 385 | if (!ArgsV.back()) 386 | return nullptr; 387 | } 388 | 389 | return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); 390 | } 391 | 392 | Function *PrototypeAST::codegen() { 393 | 394 | std::vector Doubles(Args.size(), Type::getDoubleTy(*TheContext)); 395 | FunctionType *FT = 396 | FunctionType::get(Type::getDoubleTy(*TheContext), Doubles, false); 397 | 398 | Function *F = 399 | Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get()); 400 | 401 | unsigned Idx = 0; 402 | for (auto &Arg : F->args()) 403 | Arg.setName(Args[Idx++]); 404 | 405 | return F; 406 | } 407 | 408 | Function *FunctionAST::codegen() { 409 | 410 | Function *TheFunction = TheModule->getFunction(Proto->getName()); 411 | 412 | if (!TheFunction) 413 | TheFunction = Proto->codegen(); 414 | 415 | if (!TheFunction) 416 | return nullptr; 417 | 418 | BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction); 419 | Builder->SetInsertPoint(BB); 420 | 421 | NamedValues.clear(); 422 | for (auto &Arg : TheFunction->args()) 423 | NamedValues[std::string(Arg.getName())] = &Arg; 424 | 425 | if (Value *RetVal = Body->codegen()) { 426 | Builder->CreateRet(RetVal); 427 | 428 | verifyFunction(*TheFunction); 429 | 430 | return TheFunction; 431 | } 432 | 433 | TheFunction->eraseFromParent(); 434 | return nullptr; 435 | } 436 | 437 | 438 | static void InitializeModule() { 439 | TheContext = std::make_unique(); 440 | TheModule = std::make_unique("my cool jit", *TheContext); 441 | 442 | Builder = std::make_unique>(*TheContext); 443 | } 444 | 445 | static void HandleDefinition() { 446 | if (auto FnAST = ParseDefinition()) { 447 | if (auto *FnIR = FnAST->codegen()) { 448 | fprintf(stderr, "Read function definition:"); 449 | FnIR->print(errs()); 450 | fprintf(stderr, "\n"); 451 | } 452 | } else { 453 | getNextToken(); 454 | } 455 | } 456 | 457 | static void HandleExtern() { 458 | if (auto ProtoAST = ParseExtern()) { 459 | if (auto *FnIR = ProtoAST->codegen()) { 460 | fprintf(stderr, "Read extern: "); 461 | FnIR->print(errs()); 462 | fprintf(stderr, "\n"); 463 | } 464 | } else { 465 | getNextToken(); 466 | } 467 | } 468 | 469 | static void HanldeTopLevelExpression() { 470 | if (auto FnAST = ParseTopLevelExpr()) { 471 | if (auto *FnIR = FnAST->codegen()) { 472 | fprintf(stderr, "Read top-level expression:"); 473 | FnIR->print(errs()); 474 | fprintf(stderr, "\n"); 475 | 476 | FnIR->eraseFromParent(); 477 | } 478 | } else { 479 | getNextToken(); 480 | } 481 | } 482 | 483 | static void MainLoop() { 484 | while (true) { 485 | fprintf(stderr, "ready> "); 486 | switch (CurTok) { 487 | case tok_eof: 488 | return; 489 | case ';': 490 | getNextToken(); 491 | break; 492 | case tok_def: 493 | HandleDefinition(); 494 | break; 495 | case tok_extern: 496 | HandleExtern(); 497 | break; 498 | default: 499 | HanldeTopLevelExpression(); 500 | break; 501 | } 502 | } 503 | } 504 | 505 | int main() { 506 | 507 | BinopPrecedence['<'] = 10; 508 | BinopPrecedence['+'] = 20; 509 | BinopPrecedence['-'] = 20; 510 | BinopPrecedence['*'] = 40; 511 | 512 | fprintf(stderr, "ready> "); 513 | getNextToken(); 514 | 515 | InitializeModule(); 516 | 517 | MainLoop(); 518 | 519 | TheModule->print(errs(), nullptr); 520 | 521 | return 0; 522 | } 523 | -------------------------------------------------------------------------------- /doc/Kaleidoscope04.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:添加JIT和优化器支持 2 | 3 | ## 第四章绪论 4 | 5 | 第1-3章描述了简单语言的实现,并添加了对生成LLVM IR的支持。本章介绍两种新技术:向语言添加优化器支持和添加JIT编译器支持。这些新增内容将演示如何为Kaleidoscope语言获得漂亮、高效的代码。 6 | 7 | ## 琐碎的常数折叠 8 | 9 | 我们在第3章中的演示是优雅的,并且易于扩展。不幸的是,它不能生成出色的代码。但是,在编译简单代码时,IRBuilder确实为我们提供了明显的优化: 10 | 11 | ``` 12 | ready> def test(x) 1+2+x; 13 | Read function definition: 14 | define double @test(double %x) { 15 | entry: 16 | %addtmp = fadd double 3.000000e+00, %x 17 | ret double %addtmp 18 | } 19 | ``` 20 | 21 | 此代码不是通过解析输入构建的AST的文字转录。那就是: 22 | 23 | ``` 24 | ready> def test(x) 1+2+x; 25 | Read function definition: 26 | define double @test(double %x) { 27 | entry: 28 | %addtmp = fadd double 2.000000e+00, 1.000000e+00 29 | %addtmp1 = fadd double %addtmp, %x 30 | ret double %addtmp1 31 | } 32 | ``` 33 | 34 | 特别是如上所述,常量折叠是一种非常常见且非常重要的优化:如此之多,以至于许多语言实现者在其AST表示中实现了常量折叠支持。 35 | 36 | 使用LLVM,您在AST中不需要这种支持。因为构建LLVM IR的所有调用都要通过LLVM IR生成器,所以当您调用它时,生成器本身会检查是否存在常量折叠机会。如果有,它只执行常量折叠并返回常量,而不是创建指令。 37 | 38 | 嗯,这很简单:)。实际上,我们建议在生成这样的代码时始终使用`IRBuilder`。它的使用没有“语法开销”(您不必在任何地方通过常量检查使编译器丑化),并且它可以极大地减少在某些情况下生成的LLVM IR的数量(特别是对于带有宏预处理器的语言或使用大量常量的语言)。 39 | 40 | 另一方面,“IRBuilder”受到这样一个事实的限制,即它在构建时所有的分析都与代码内联。如果您举一个稍微复杂一点的示例: 41 | 42 | ``` 43 | ready> def test(x) (1+2+x)*(x+(1+2)); 44 | ready> Read function definition: 45 | define double @test(double %x) { 46 | entry: 47 | %addtmp = fadd double 3.000000e+00, %x 48 | %addtmp1 = fadd double %x, 3.000000e+00 49 | %multmp = fmul double %addtmp, %addtmp1 50 | ret double %multmp 51 | } 52 | ``` 53 | 54 | 在这种情况下,乘法的LHS和RHS是相同的值。我们非常希望看到它生成“`tmp=x+3;result=tmp*tmp;`”,而不是计算“`x+3`”两次。 55 | 56 | 不幸的是,任何数量的本地分析都无法检测和纠正这一点。这需要两个转换:表达式的重新关联(以使加法的词法相同)和公共子表达式消除(CSE)以删除冗余的加法指令。幸运的是,LLVM以“PASS”的形式提供了一系列可以使用的优化。 57 | 58 | ## LLVM优化通过 59 | 60 | > 警告:由于已过渡到新的PassManager基础结构,因此本教程基于`llvm::Legacy::FunctionPassManager`(可以在[LegacyPassManager.h](https://llvm.org/doxygen/classllvm_1_1legacy_1_1FunctionPassManager.html)中找到).在完成PASS管理器过渡之前,应一直使用上述PassManager。 61 | 62 | LLVM提供了许多优化通道,它们可以做很多不同的事情,有不同的权衡。与其他系统不同的是,LLVM不会错误地认为一组优化对所有语言和所有情况都是正确的。LLVM允许编译器实现者完全决定使用什么优化、以什么顺序和在什么情况下使用。 63 | 64 | 作为一个具体示例,LLVM支持两个“整个模块(whole module)”passes,这两个过程都能看到尽可能完整的代码体(通常是整个文件,但如果在链接时运行,这可能是整个程序的重要部分)。它还支持并包含“每个函数(per function)”passes,这些传递一次只在一个函数上操作,而不查看其他函数。有关pass及其运行方式的更多信息,请参阅[如何编写pass](https://llvm.org/docs/WritingAnLLVMPass.html)文档和[LLVM pass列表](https://llvm.org/docs/Passes.html)。 65 | 66 | 对于Kaleidoscope来说,我们目前正在动态(on the fly)生成函数,随着用户输入函数,一次生成一个函数。我们的目标不是在这种设置下获得终极优化体验,但我们也希望尽可能捕捉到简单快捷的东西。因此,我们将选择在用户键入函数时针对每个函数运行一些优化。如果我们想要创建一个“静态Kaleidoscope编译器”,我们将完全使用现在拥有的代码,只是我们将推迟运行优化器,直到解析完整个文件。 67 | 68 | 为了运行每个函数的优化,我们需要设置一个[FunctionPassManager](https://llvm.org/docs/WritingAnLLVMPass.html#what-passmanager-doesr)来保存和组织我们想要运行的LLVM优化。一旦我们有了这些,我们就可以添加一组要运行的优化。我们需要为每个要优化的模块创建一个新的FunctionPassManager,因此我们将编写一个函数来为我们创建和初始化模块和Pass管理器: 69 | 70 | ```c++ 71 | void InitializeModuleAndPassManager(void) { 72 | // Open a new module. 73 | TheModule = std::make_unique("my cool jit", TheContext); 74 | 75 | // Create a new pass manager attached to it. 76 | TheFPM = std::make_unique(TheModule.get()); 77 | 78 | // Do simple "peephole" optimizations and bit-twiddling optzns. 79 | TheFPM->add(createInstructionCombiningPass()); 80 | // Reassociate expressions. 81 | TheFPM->add(createReassociatePass()); 82 | // Eliminate Common SubExpressions. 83 | TheFPM->add(createGVNPass()); 84 | // Simplify the control flow graph (deleting unreachable blocks, etc). 85 | TheFPM->add(createCFGSimplificationPass()); 86 | 87 | TheFPM->doInitialization(); 88 | } 89 | ``` 90 | 91 | 此代码初始化全局模块`TheModule`,以及`TheModule`附带的函数pass管理器`TheFPM`。一旦设置了PASS管理器,我们将使用一系列的“add”调用来添加一组LLVM PASS。 92 | 93 | 在本例中,我们选择添加四个优化过程。我们在这里选择的通道是一组非常标准的“清理”优化,对各种代码都很有用。我不会深入研究他们做了什么,但相信我,他们是一个很好的起点:)。 94 | 95 | 一旦设置了PassManager,我们就需要使用它。我们在构造新创建的函数之后(在`FunctionAST::codegen()`中),在返回给客户端之前运行: 96 | 97 | ```c++ 98 | if (Value *RetVal = Body->codegen()) { 99 | // Finish off the function. 100 | Builder.CreateRet(RetVal); 101 | 102 | // Validate the generated code, checking for consistency. 103 | verifyFunction(*TheFunction); 104 | 105 | // Optimize the function. 106 | TheFPM->run(*TheFunction); 107 | 108 | return TheFunction; 109 | } 110 | ``` 111 | 112 | 如您所见,这非常简单。`FunctionPassManager`就地优化和更新LLVM函数\*,改进(希望如此)它的主体。准备就绪后,我们可以再次尝试上面的测试: 113 | 114 | ``` 115 | ready> def test(x) (1+2+x)*(x+(1+2)); 116 | ready> Read function definition: 117 | define double @test(double %x) { 118 | entry: 119 | %addtmp = fadd double %x, 3.000000e+00 120 | %multmp = fmul double %addtmp, %addtmp 121 | ret double %multmp 122 | } 123 | ``` 124 | 125 | 不出所料,我们现在得到了经过良好优化的代码,每次执行此函数时都会保存一条浮点加法指令。 126 | 127 | LLVM提供了可在某些情况下使用的各种优化。虽然有一些[各种pass的文档](https://llvm.org/docs/Passes.html),但不是很完整。另一个很好的想法来源是查看`Clang`开始运行的pass来学习pass。“`opt`”工具允许您从命令行尝试pass,这样您就可以看到它们是否有什么作用。 128 | 129 | 现在我们有了来自前端的合理代码,让我们来讨论一下如何执行它! 130 | 131 | ## 添加JIT编译器 132 | 133 | LLVM IR中提供的代码可以应用多种工具。例如,您可以对其运行优化(如上所述),可以将其转储为文本或二进制形式,可以将代码编译为某个目标的汇编文件(.s),也可以对其进行JIT编译。LLVM IR表示的好处是它是编译器许多不同部分之间的“通用货币”。 134 | 135 | 在本节中,我们将在我们的解释器中添加JIT编译器支持。我们希望Kaleidoscope的基本思想是让用户像现在一样输入函数体,但立即计算他们键入的顶层表达式。例如,如果他们键入“1+2;”,我们应该计算并打印出3。如果他们定义了函数,他们应该能够从命令行调用该函数。 136 | 137 | 为此,我们首先准备环境为当前本机目标创建代码,并声明和初始化JIT。方法是调用一些`InitializeNativeTarget\*`函数,添加一个全局变量`TheJIT`,在`main`中初始化: 138 | 139 | ```c++ 140 | static std::unique_ptr TheJIT; 141 | ... 142 | int main() { 143 | InitializeNativeTarget(); 144 | InitializeNativeTargetAsmPrinter(); 145 | InitializeNativeTargetAsmParser(); 146 | 147 | // Install standard binary operators. 148 | // 1 is lowest precedence. 149 | BinopPrecedence['<'] = 10; 150 | BinopPrecedence['+'] = 20; 151 | BinopPrecedence['-'] = 20; 152 | BinopPrecedence['*'] = 40; // highest. 153 | 154 | // Prime the first token. 155 | fprintf(stderr, "ready> "); 156 | getNextToken(); 157 | 158 | TheJIT = std::make_unique(); 159 | 160 | // Run the main "interpreter loop" now. 161 | MainLoop(); 162 | 163 | return 0; 164 | } 165 | ``` 166 | 167 | 我们还需要设置JIT的数据布局: 168 | 169 | ```c++ 170 | void InitializeModuleAndPassManager(void) { 171 | // Open a new module. 172 | TheModule = std::make_unique("my cool jit", TheContext); 173 | TheModule->setDataLayout(TheJIT->getTargetMachine().createDataLayout()); 174 | 175 | // Create a new pass manager attached to it. 176 | TheFPM = std::make_unique(TheModule.get()); 177 | ... 178 | ``` 179 | 180 | KaleidoscopeJIT类是专门为这些教程构建的简单JIT类,可在llvm-src/examples/Kaleidoscope/include/KaleidoscopeJIT.h.的LLVM源代码中找到。在后面的章节中,我们将看看它是如何工作的,并用新功能对其进行扩展,但现在我们将把它当作给定的。它的接口非常简单:`addModule`将LLVM IR模块添加到JIT中,使其函数可供执行;`removeModule`移除模块,释放与该模块中的代码关联的所有内存;`findSymbol`允许我们查找指向编译后代码的指针。 181 | 182 | 我们可以使用这个简单的API,并将解析顶层表达式的代码更改为如下所示: 183 | 184 | ```c++ 185 | static void HandleTopLevelExpression() { 186 | // Evaluate a top-level expression into an anonymous function. 187 | if (auto FnAST = ParseTopLevelExpr()) { 188 | if (FnAST->codegen()) { 189 | 190 | // JIT the module containing the anonymous expression, keeping a handle so 191 | // we can free it later. 192 | auto H = TheJIT->addModule(std::move(TheModule)); 193 | InitializeModuleAndPassManager(); 194 | 195 | // Search the JIT for the __anon_expr symbol. 196 | auto ExprSymbol = TheJIT->findSymbol("__anon_expr"); 197 | assert(ExprSymbol && "Function not found"); 198 | 199 | // Get the symbol's address and cast it to the right type (takes no 200 | // arguments, returns a double) so we can call it as a native function. 201 | double (*FP)() = (double (*)())(intptr_t)ExprSymbol.getAddress(); 202 | fprintf(stderr, "Evaluated to %f\n", FP()); 203 | 204 | // Delete the anonymous expression module from the JIT. 205 | TheJIT->removeModule(H); 206 | } 207 | ``` 208 | 209 | 如果解析和编码生成成功,则下一步是将包含顶层表达式的模块添加到JIT。我们通过调用addModule来实现这一点,addModule触发模块中所有函数的代码生成,并返回一个句柄,该句柄可用于稍后从JIT中删除模块。模块一旦添加到JIT中就不能再修改,所以我们还会通过调用`InitializeModuleAndPassManager()`打开一个新模块来存放后续代码。 210 | 211 | 将模块添加到JIT后,我们需要获取指向最终生成的代码的指针。为此,我们调用JIT的findSymbol方法,并传递顶层表达式函数的名称:`__anon_expr`。由于我们刚刚添加了此函数,因此我们断言findSymbol返回了一个结果。 212 | 213 | 接下来,我们通过对符号调用`getAddress()`来获取`__anon_expr`函数的内存地址。回想一下,我们将顶层表达式编译成一个不带参数并返回计算出的双精度值的自包含LLVM函数。因为LLVM JIT编译器匹配本机平台ABI,这意味着您只需将结果指针转换为该类型的函数指针并直接调用它。这意味着,JIT编译代码和静态链接到应用程序中的本机代码之间没有区别。 214 | 215 | 最后,因为我们不支持顶层表达式的重新求值,所以当我们完成释放相关内存时,我们会从JIT中删除该模块。但是,回想一下,我们在前面几行创建的模块(通过`InitializeModuleAndPassManager`)仍然处于打开状态,并等待添加新代码。 216 | 217 | 仅凭这两个变化,让我们看看Kaleidoscope现在是如何工作的! 218 | 219 | ``` 220 | ready> 4+5; 221 | Read top-level expression: 222 | define double @0() { 223 | entry: 224 | ret double 9.000000e+00 225 | } 226 | 227 | Evaluated to 9.000000 228 | ``` 229 | 230 | 嗯,这看起来基本上是有效的。函数的转储显示了我们为每个键入的顶层表达式合成的“总是返回双精度的无参数函数”。这演示了非常基本的功能,但是我们能做更多吗? 231 | 232 | ``` 233 | ready> def testfunc(x y) x + y*2; 234 | Read function definition: 235 | define double @testfunc(double %x, double %y) { 236 | entry: 237 | %multmp = fmul double %y, 2.000000e+00 238 | %addtmp = fadd double %multmp, %x 239 | ret double %addtmp 240 | } 241 | 242 | ready> testfunc(4, 10); 243 | Read top-level expression: 244 | define double @1() { 245 | entry: 246 | %calltmp = call double @testfunc(double 4.000000e+00, double 1.000000e+01) 247 | ret double %calltmp 248 | } 249 | 250 | Evaluated to 24.000000 251 | 252 | ready> testfunc(5, 10); 253 | ready> LLVM ERROR: Program used external function 'testfunc' which could not be resolved! 254 | ``` 255 | 256 | 函数定义和调用也可以工作,但最后一行出现了非常错误的情况。函数调用看起来有效,但是出现报错,发生了什么事?正如您可能从API中猜到的那样,Module是JIT的分配单元,而testfunc是包含匿名表达式的同一模块的一部分。当我们从JIT中删除该模块以释放用于匿名表达式的内存时,我们同时删除了`testfunc`的定义。然后,当我们试图第二次调用testfunc时,JIT再也找不到它了。 257 | 258 | 解决此问题的最简单方法是将匿名表达式放在与剩余函数定义的不同的模块中。JIT将愉快地跨模块边界解决函数调用,只要每个被调用的函数都有一个原型,并且在调用之前被添加到JIT中。通过将匿名表达式放在不同的模块中,我们可以删除它,而不会影响剩余的函数。 259 | 260 | 事实上,我们将更进一步,将每个函数都放在它自己的模块中。这样做可以利用KaleidoscopeJIT的一个有用属性,这将使我们的环境更像REPL(Read–eval–print loop):函数可以多次添加到JIT中(不同于每个函数都必须有唯一定义的模块)。当您在KaleidoscopeJIT中查找符号时,它将始终返回最新的定义: 261 | 262 | ``` 263 | ready> def foo(x) x + 1; 264 | Read function definition: 265 | define double @foo(double %x) { 266 | entry: 267 | %addtmp = fadd double %x, 1.000000e+00 268 | ret double %addtmp 269 | } 270 | 271 | ready> foo(2); 272 | Evaluated to 3.000000 273 | 274 | ready> def foo(x) x + 2; 275 | define double @foo(double %x) { 276 | entry: 277 | %addtmp = fadd double %x, 2.000000e+00 278 | ret double %addtmp 279 | } 280 | 281 | ready> foo(2); 282 | Evaluated to 4.000000 283 | ``` 284 | 285 | 要允许每个函数驻留在其自己的模块中,我们需要一种方法将以前的函数声明重新生成到我们打开的每个新模块中: 286 | 287 | ```c++ 288 | static std::unique_ptr TheJIT; 289 | 290 | ... 291 | 292 | Function *getFunction(std::string Name) { 293 | // First, see if the function has already been added to the current module. 294 | if (auto *F = TheModule->getFunction(Name)) 295 | return F; 296 | 297 | // If not, check whether we can codegen the declaration from some existing 298 | // prototype. 299 | auto FI = FunctionProtos.find(Name); 300 | if (FI != FunctionProtos.end()) 301 | return FI->second->codegen(); 302 | 303 | // If no existing prototype exists, return null. 304 | return nullptr; 305 | } 306 | 307 | ... 308 | 309 | Value *CallExprAST::codegen() { 310 | // Look up the name in the global module table. 311 | Function *CalleeF = getFunction(Callee); 312 | 313 | ... 314 | 315 | Function *FunctionAST::codegen() { 316 | // Transfer ownership of the prototype to the FunctionProtos map, but keep a 317 | // reference to it for use below. 318 | auto &P = *Proto; 319 | FunctionProtos[Proto->getName()] = std::move(Proto); 320 | Function *TheFunction = getFunction(P.getName()); 321 | if (!TheFunction) 322 | return nullptr; 323 | ``` 324 | 325 | 要实现这一点,我们将从添加一个新的全局`FunctionProtos`开始,它保存每个函数的最新原型。我们还将添加一个方便的方法`getFunction()`来替换对`TheModule->getFunction()`的调用。我们的便捷方法在`TheModule`中搜索现有的函数声明,如果没有找到,则退回到从FunctionProtos生成新的声明。在`CallExprAST::codegen()`中,我们只需要替换对`TheModule->getFunction()`的调用。在`FunctionAST::codegen()`中,我们需要先更新FunctionProtos映射,然后再调用`getFunction()`。完成此操作后,我们始终可以在当前模块中为任何先前声明的函数获取函数声明。 326 | 327 | 我们还需要更新HandleDefinition和HandleExtern: 328 | 329 | ```c++ 330 | static void HandleDefinition() { 331 | if (auto FnAST = ParseDefinition()) { 332 | if (auto *FnIR = FnAST->codegen()) { 333 | fprintf(stderr, "Read function definition:"); 334 | FnIR->print(errs()); 335 | fprintf(stderr, "\n"); 336 | TheJIT->addModule(std::move(TheModule)); 337 | InitializeModuleAndPassManager(); 338 | } 339 | } else { 340 | // Skip token for error recovery. 341 | getNextToken(); 342 | } 343 | } 344 | 345 | static void HandleExtern() { 346 | if (auto ProtoAST = ParseExtern()) { 347 | if (auto *FnIR = ProtoAST->codegen()) { 348 | fprintf(stderr, "Read extern: "); 349 | FnIR->print(errs()); 350 | fprintf(stderr, "\n"); 351 | FunctionProtos[ProtoAST->getName()] = std::move(ProtoAST); 352 | } 353 | } else { 354 | // Skip token for error recovery. 355 | getNextToken(); 356 | } 357 | } 358 | ``` 359 | 360 | 在HandleDefinition中,我们添加两行代码来将新定义的函数传递给JIT并打开一个新模块。在HandleExtern中,我们只需要添加一行将原型添加到FunctionProtos。 361 | 362 | 完成这些更改后,让我们再次尝试我们的REPL(这次我删除了匿名函数的转储,您现在应该明白了): 363 | ``` 364 | ready> def foo(x) x + 1; 365 | ready> foo(2); 366 | Evaluated to 3.000000 367 | 368 | ready> def foo(x) x + 2; 369 | ready> foo(2); 370 | Evaluated to 4.000000 371 | ``` 372 | 373 | 它是有效的! 374 | 375 | 即使采用了这么简单的代码,我们收获了令人惊讶的强大能力 - 来看看下面示例: 376 | 377 | ``` 378 | ready> extern sin(x); 379 | Read extern: 380 | declare double @sin(double) 381 | 382 | ready> extern cos(x); 383 | Read extern: 384 | declare double @cos(double) 385 | 386 | ready> sin(1.0); 387 | Read top-level expression: 388 | define double @2() { 389 | entry: 390 | ret double 0x3FEAED548F090CEE 391 | } 392 | 393 | Evaluated to 0.841471 394 | 395 | ready> def foo(x) sin(x)*sin(x) + cos(x)*cos(x); 396 | Read function definition: 397 | define double @foo(double %x) { 398 | entry: 399 | %calltmp = call double @sin(double %x) 400 | %multmp = fmul double %calltmp, %calltmp 401 | %calltmp2 = call double @cos(double %x) 402 | %multmp4 = fmul double %calltmp2, %calltmp2 403 | %addtmp = fadd double %multmp, %multmp4 404 | ret double %addtmp 405 | } 406 | 407 | ready> foo(4.0); 408 | Read top-level expression: 409 | define double @3() { 410 | entry: 411 | %calltmp = call double @foo(double 4.000000e+00) 412 | ret double %calltmp 413 | } 414 | 415 | Evaluated to 1.000000 416 | ``` 417 | 418 | 哇,JIT怎么知道SIN和COS的?答案出奇的简单:KaleidoscopeJIT有一个简单明了的符号解析规则,它用来查找任何给定模块中没有的符号:首先,它搜索已经添加到JIT的所有模块(从最新到最旧),以找到最新的定义。如果在JIT中找不到定义,它将退回到在Kaleidoscope进程本身上调用“`dlsym(”sin“)`”。因为“`sin`”是在JIT的地址空间中定义的,所以它只是给模块中的调用打了补丁,直接调用`sin`的libm版本。但在某些情况下,这甚至会更进一步:因为sin和cos是标准数学函数的名称,所以当使用常量调用函数时,Constant folder将直接计算函数调用的正确结果,就像上面的“`sin(1.0)`”一样。 419 | 420 | 在未来,我们将看到调整此符号解析规则能够被用来启用各种有用的功能,从安全性(限制可用于JIT代码的符号集)到基于符号名称的动态代码生成,甚至惰性编译(lazy compilation)。 421 | 422 | 符号解析规则的一个直接好处是,我们现在可以通过编写任意C++代码来实现来扩展语言操作符operation。例如,如果我们添加: 423 | 424 | ```c++ 425 | #ifdef _WIN32 426 | #define DLLEXPORT __declspec(dllexport) 427 | #else 428 | #define DLLEXPORT 429 | #endif 430 | 431 | /// putchard - putchar that takes a double and returns 0. 432 | extern "C" DLLEXPORT double putchard(double X) { 433 | fputc((char)X, stderr); 434 | return 0; 435 | } 436 | ``` 437 | 438 | 请注意,对于Windows,我们需要实际导出函数,因为动态符号加载器将使用GetProcAddress查找符号。 439 | 440 | 现在,我们可以使用以下命令向控制台生成简单的输出:“`extern putchard(X);putchard(120);`”,它在控制台上打印小写的‘x’(120是‘x’的ASCII代码)。类似的代码可用于在Kaleidoscope中实现文件I/O、控制台输入和许多其他功能。 441 | 442 | 这就完成了Kaleidoscope教程的JIT和优化器一章。在这一点上,我们可以编译一种非图灵完全的编程语言,并以用户驱动的方式对其进行优化和JIT编译。接下来,我们将研究[使用控制流构造扩展语言](Kaleidoscope05.md),解决一些有趣的LLVM IR问题。 443 | 444 | 445 | [下一步:扩展语言:控制流](Kaleidoscope05.md) 446 | -------------------------------------------------------------------------------- /src/ch4_jit.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by m0dulo on 2022/12/03. 3 | // 4 | 5 | #include "./include/KaleidoscopeJIT.h" 6 | #include "llvm/ADT/APFloat.h" 7 | #include "llvm/ADT/STLExtras.h" 8 | #include "llvm/IR/BasicBlock.h" 9 | #include "llvm/IR/Constants.h" 10 | #include "llvm/IR/DerivedTypes.h" 11 | #include "llvm/IR/Function.h" 12 | #include "llvm/IR/IRBuilder.h" 13 | #include "llvm/IR/LLVMContext.h" 14 | #include "llvm/IR/LegacyPassManager.h" 15 | #include "llvm/IR/Module.h" 16 | #include "llvm/IR/Type.h" 17 | #include "llvm/IR/Verifier.h" 18 | #include "llvm/Support/TargetSelect.h" 19 | #include "llvm/Target/TargetMachine.h" 20 | #include "llvm/Transforms/InstCombine/InstCombine.h" 21 | #include "llvm/Transforms/Scalar.h" 22 | #include "llvm/Transforms/Scalar/GVN.h" 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | using namespace llvm; 35 | using namespace llvm::orc; 36 | 37 | enum Token { 38 | tok_eof = -1, 39 | tok_def = -2, 40 | tok_extern = -3, 41 | 42 | tok_identifier = -4, 43 | tok_number = -5 44 | }; 45 | 46 | static std::string IdentifierStr; 47 | static double NumVal; 48 | 49 | static int gettok() { 50 | static int LastChar = ' '; 51 | 52 | while (isspace(LastChar)) 53 | LastChar = getchar(); 54 | 55 | if (isalpha(LastChar)) { 56 | IdentifierStr = LastChar; 57 | while (isalnum(LastChar = getchar())) 58 | IdentifierStr += LastChar; 59 | 60 | if (IdentifierStr == "def") 61 | return tok_def; 62 | if (IdentifierStr == "extern") 63 | return tok_extern; 64 | return tok_identifier; 65 | } 66 | 67 | if (isdigit(LastChar) || LastChar == '.') { 68 | std::string NumStr; 69 | do { 70 | NumStr += LastChar; 71 | LastChar = getchar(); 72 | } while (isdigit(LastChar) || LastChar == '.'); 73 | 74 | NumVal = strtod(NumStr.c_str(), nullptr); 75 | return tok_number; 76 | } 77 | 78 | if (LastChar == '#') { 79 | do 80 | LastChar = getchar(); 81 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 82 | 83 | if (LastChar != EOF) 84 | return gettok(); 85 | } 86 | 87 | if (LastChar == EOF) 88 | return tok_eof; 89 | 90 | int ThisChar = LastChar; 91 | LastChar = getchar(); 92 | return ThisChar; 93 | } 94 | 95 | 96 | namespace { 97 | 98 | class ExprAST { 99 | public: 100 | virtual ~ExprAST() = default; 101 | virtual Value *codegen() = 0; 102 | }; 103 | 104 | class NumberExprAST : public ExprAST { 105 | double Val; 106 | 107 | public: 108 | NumberExprAST(double Val) : Val(Val) {} 109 | Value *codegen() override; 110 | }; 111 | 112 | class VariableExprAST : public ExprAST { 113 | std::string Name; 114 | 115 | public: 116 | VariableExprAST(const std::string &Name) : Name(Name) {} 117 | Value *codegen() override; 118 | }; 119 | 120 | class BinaryExprAST : public ExprAST { 121 | char Op; 122 | std::unique_ptr LHS, RHS; 123 | 124 | public: 125 | BinaryExprAST(char Op, std::unique_ptr LHS, 126 | std::unique_ptr RHS) 127 | : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 128 | Value *codegen() override; 129 | }; 130 | 131 | class CallExprAST : public ExprAST { 132 | std::string Callee; 133 | std::vector> Args; 134 | 135 | public: 136 | CallExprAST(const std::string &Callee, 137 | std::vector> Args) 138 | : Callee(Callee), Args(std::move(Args)) {} 139 | Value *codegen() override; 140 | }; 141 | 142 | class PrototypeAST { 143 | std::string Name; 144 | std::vector Args; 145 | 146 | public: 147 | PrototypeAST(const std::string &Name, std::vector Args) 148 | : Name(Name), Args(std::move(Args)) {} 149 | 150 | const std::string &getName() const { return Name; } 151 | Function *codegen(); 152 | }; 153 | 154 | class FunctionAST { 155 | std::unique_ptr Proto; 156 | std::unique_ptr Body; 157 | 158 | public: 159 | FunctionAST(std::unique_ptr Proto, 160 | std::unique_ptr Body) 161 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 162 | Function *codegen(); 163 | }; 164 | 165 | } 166 | 167 | static int CurTok; 168 | static int getNextToken() { return CurTok = gettok(); } 169 | 170 | static std::map BinopPrecedence; 171 | 172 | static int GetTokPrecedence() { 173 | if (!isascii(CurTok)) 174 | return -1; 175 | 176 | int TokPrec = BinopPrecedence[CurTok]; 177 | if (TokPrec <= 0) 178 | return -1; 179 | return TokPrec; 180 | } 181 | 182 | std::unique_ptr LogError(const char *Str) { 183 | fprintf(stderr, "Error: %s\n", Str); 184 | return nullptr; 185 | } 186 | 187 | std::unique_ptr LogErrorP(const char *Str) { 188 | LogError(Str); 189 | return nullptr; 190 | } 191 | 192 | static std::unique_ptr ParseExpression(); 193 | 194 | static std::unique_ptr ParseNumberExpr() { 195 | auto Result = std::make_unique(NumVal); 196 | getNextToken(); 197 | return std::move(Result); 198 | } 199 | 200 | static std::unique_ptr ParseParenExpr() { 201 | getNextToken(); 202 | auto V = ParseExpression(); 203 | if (!V) 204 | return nullptr; 205 | 206 | if (CurTok != ')') 207 | return LogError("expected ')'"); 208 | getNextToken(); 209 | return V; 210 | } 211 | 212 | static std::unique_ptr ParseIdentifierExpr() { 213 | std::string IdName = IdentifierStr; 214 | 215 | getNextToken(); 216 | 217 | if (CurTok != '(') 218 | return std::make_unique(IdName); 219 | 220 | getNextToken(); 221 | std::vector> Args; 222 | if (CurTok != ')') { 223 | while (true) { 224 | if (auto Arg = ParseExpression()) 225 | Args.push_back(std::move(Arg)); 226 | else 227 | return nullptr; 228 | 229 | if (CurTok == ')') 230 | break; 231 | 232 | if (CurTok != ',') 233 | return LogError("Expected ')' or ',' in argument list"); 234 | getNextToken(); 235 | } 236 | } 237 | 238 | getNextToken(); 239 | 240 | return std::make_unique(IdName, std::move(Args)); 241 | } 242 | 243 | static std::unique_ptr ParsePrimary() { 244 | switch (CurTok) { 245 | default: 246 | return LogError("unknown token when expecting an expression"); 247 | case tok_identifier: 248 | return ParseIdentifierExpr(); 249 | case tok_number: 250 | return ParseNumberExpr(); 251 | case '(': 252 | return ParseParenExpr(); 253 | } 254 | } 255 | 256 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 257 | std::unique_ptr LHS) { 258 | while (true) { 259 | int TokPrec = GetTokPrecedence(); 260 | 261 | if (TokPrec < ExprPrec) 262 | return LHS; 263 | 264 | int BinOp = CurTok; 265 | getNextToken(); 266 | 267 | auto RHS = ParsePrimary(); 268 | if (!RHS) 269 | return nullptr; 270 | 271 | int NextPrec = GetTokPrecedence(); 272 | if (TokPrec < NextPrec) { 273 | RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS)); 274 | if (!RHS) 275 | return nullptr; 276 | } 277 | 278 | LHS = 279 | std::make_unique(BinOp, std::move(LHS), std::move(RHS)); 280 | } 281 | } 282 | 283 | static std::unique_ptr ParseExpression() { 284 | auto LHS = ParsePrimary(); 285 | if (!LHS) 286 | return nullptr; 287 | 288 | return ParseBinOpRHS(0, std::move(LHS)); 289 | } 290 | 291 | static std::unique_ptr ParsePrototype() { 292 | if (CurTok != tok_identifier) 293 | return LogErrorP("Expected function name in prototype"); 294 | 295 | std::string FnName = IdentifierStr; 296 | getNextToken(); 297 | 298 | if (CurTok != '(') 299 | return LogErrorP("Expected '(' in prototype"); 300 | 301 | std::vector ArgNames; 302 | while (getNextToken() == tok_identifier) 303 | ArgNames.push_back(IdentifierStr); 304 | if (CurTok != ')') 305 | return LogErrorP("Expected ')' in prototype"); 306 | 307 | getNextToken(); 308 | 309 | return std::make_unique(FnName, std::move(ArgNames)); 310 | } 311 | 312 | static std::unique_ptr ParseDefinition() { 313 | getNextToken(); 314 | auto Proto = ParsePrototype(); 315 | if (!Proto) 316 | return nullptr; 317 | 318 | if (auto E = ParseExpression()) 319 | return std::make_unique(std::move(Proto), std::move(E)); 320 | return nullptr; 321 | } 322 | 323 | static std::unique_ptr ParseTopLevelExpr() { 324 | if (auto E = ParseExpression()) { 325 | auto Proto = std::make_unique("__anon_expr", 326 | std::vector()); 327 | return std::make_unique(std::move(Proto), std::move(E)); 328 | } 329 | return nullptr; 330 | } 331 | 332 | static std::unique_ptr ParseExtern() { 333 | getNextToken(); 334 | return ParsePrototype(); 335 | } 336 | 337 | static std::unique_ptr TheContext; 338 | static std::unique_ptr TheModule; 339 | static std::unique_ptr> Builder; 340 | static std::map NamedValues; 341 | 342 | static std::unique_ptr TheFPM; 343 | static std::unique_ptr TheJIT; 344 | static std::map> FunctionProtos; 345 | static ExitOnError ExitOnErr; 346 | 347 | Value *LogErrorV(const char *Str) { 348 | LogError(Str); 349 | return nullptr; 350 | } 351 | 352 | Function *getFunction(std::string Name) { 353 | if (auto *F = TheModule->getFunction(Name)) 354 | return F; 355 | 356 | auto FI = FunctionProtos.find(Name); 357 | if (FI != FunctionProtos.end()) 358 | return FI->second->codegen(); 359 | 360 | return nullptr; 361 | } 362 | 363 | Value *NumberExprAST::codegen() { 364 | return ConstantFP::get(*TheContext, APFloat(Val)); 365 | } 366 | 367 | Value *VariableExprAST::codegen() { 368 | Value *V = NamedValues[Name]; 369 | if (!V) 370 | return LogErrorV("Unknown variable name"); 371 | return V; 372 | } 373 | 374 | Value *BinaryExprAST::codegen() { 375 | Value *L = LHS->codegen(); 376 | Value *R = RHS->codegen(); 377 | if (!L || !R) 378 | return nullptr; 379 | 380 | switch (Op) { 381 | case '+': 382 | return Builder->CreateFAdd(L, R, "addtmp"); 383 | case '-': 384 | return Builder->CreateFSub(L, R, "subtmp"); 385 | case '*': 386 | return Builder->CreateFMul(L, R, "multmp"); 387 | case '<': 388 | L = Builder->CreateFCmpULT(L, R, "cmptmp"); 389 | // Convert bool 0/1 to double 0.0 or 1.0 390 | return Builder->CreateUIToFP(L, Type::getDoubleTy(*TheContext), "booltmp"); 391 | default: 392 | return LogErrorV("invalid binary operator"); 393 | } 394 | } 395 | 396 | Value *CallExprAST::codegen() { 397 | Function *CalleeF = TheModule->getFunction(Callee); 398 | if (!CalleeF) 399 | return LogErrorV("Unknown function referenced"); 400 | if (CalleeF->arg_size() != Args.size()) 401 | return LogErrorV("Incorrect # arguments passed"); 402 | 403 | std::vector ArgsV; 404 | for (unsigned i = 0, e = Args.size(); i != e; ++i) { 405 | ArgsV.push_back(Args[i]->codegen()); 406 | if (!ArgsV.back()) 407 | return nullptr; 408 | } 409 | 410 | return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); 411 | } 412 | 413 | Function *PrototypeAST::codegen() { 414 | std::vector Doubles(Args.size(), Type::getDoubleTy(*TheContext)); 415 | FunctionType *FT = 416 | FunctionType::get(Type::getDoubleTy(*TheContext), Doubles, false); 417 | 418 | Function *F = 419 | Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get()); 420 | unsigned Idx = 0; 421 | for (auto &Arg : F->args()) 422 | Arg.setName(Args[Idx++]); 423 | 424 | return F; 425 | } 426 | 427 | Function *FunctionAST::codegen() { 428 | auto &P = *Proto; 429 | FunctionProtos[Proto->getName()] = std::move(Proto); 430 | Function *TheFunction = getFunction(P.getName()); 431 | 432 | if (!TheFunction) 433 | return nullptr; 434 | 435 | BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction); 436 | Builder->SetInsertPoint(BB); 437 | 438 | NamedValues.clear(); 439 | for (auto &Arg : TheFunction->args()) 440 | NamedValues[std::string(Arg.getName())] = &Arg; 441 | 442 | if (Value *RetVal = Body->codegen()) { 443 | Builder->CreateRet(RetVal); 444 | 445 | verifyFunction(*TheFunction); 446 | 447 | return TheFunction; 448 | } 449 | 450 | TheFunction->eraseFromParent(); 451 | return nullptr; 452 | } 453 | 454 | 455 | static void InitializeModuleAndPassManager() { 456 | TheContext = std::make_unique(); 457 | TheModule = std::make_unique("my cool jit", *TheContext); 458 | TheModule->setDataLayout(TheJIT->getDataLayout()); 459 | 460 | Builder = std::make_unique>(*TheContext); 461 | 462 | TheFPM = std::make_unique(TheModule.get()); 463 | 464 | TheFPM->add(createInstructionCombiningPass()); 465 | TheFPM->add(createReassociatePass()); 466 | TheFPM->add(createGVNPass()); 467 | TheFPM->add(createCFGSimplificationPass()); 468 | 469 | TheFPM->doInitialization(); 470 | } 471 | 472 | static void HandleDefinition() { 473 | if (auto FnAST = ParseDefinition()) { 474 | if (auto *FnIR = FnAST->codegen()) { 475 | fprintf(stderr, "Read function definition:"); 476 | FnIR->print(errs()); 477 | fprintf(stderr, "\n"); 478 | ExitOnErr(TheJIT->addModule( 479 | ThreadSafeModule(std::move(TheModule), std::move(TheContext)))); 480 | InitializeModuleAndPassManager(); 481 | } 482 | } else { 483 | getNextToken(); 484 | } 485 | } 486 | 487 | static void HandleExtern() { 488 | if (auto ProtoAST = ParseExtern()) { 489 | if (auto *FnIR = ProtoAST->codegen()) { 490 | fprintf(stderr, "Read extern: "); 491 | FnIR->print(errs()); 492 | fprintf(stderr, "\n"); 493 | FunctionProtos[ProtoAST->getName()] = std::move(ProtoAST); 494 | } 495 | } else { 496 | getNextToken(); 497 | } 498 | } 499 | 500 | static void HanldeTopLevelExpression() { 501 | if (auto FnAST = ParseTopLevelExpr()) { 502 | if (auto *FnIR = FnAST->codegen()) { 503 | auto RT = TheJIT->getMainJITDylib().createResourceTracker(); 504 | 505 | auto TSM = ThreadSafeModule(std::move(TheModule), std::move(TheContext)); 506 | ExitOnErr(TheJIT->addModule(std::move(TSM), RT)); 507 | InitializeModuleAndPassManager(); 508 | 509 | auto ExprSymbol = ExitOnErr(TheJIT->lookup("__anon_expr")); 510 | 511 | double (*FP)() = (double (*)())(intptr_t)ExprSymbol.getAddress(); 512 | fprintf(stderr, "Evaluated to %f\n", FP()); 513 | 514 | ExitOnErr(RT->remove()); 515 | } 516 | } else { 517 | getNextToken(); 518 | } 519 | } 520 | 521 | static void MainLoop() { 522 | while (true) { 523 | fprintf(stderr, "ready> "); 524 | switch (CurTok) { 525 | case tok_eof: 526 | return; 527 | case ';': 528 | getNextToken(); 529 | break; 530 | case tok_def: 531 | HandleDefinition(); 532 | break; 533 | case tok_extern: 534 | HandleExtern(); 535 | break; 536 | default: 537 | HanldeTopLevelExpression(); 538 | break; 539 | } 540 | } 541 | } 542 | 543 | 544 | #ifdef _WIN32 545 | #define DLLEXPORT __declspec(dllexport) 546 | #else 547 | #define DLLEXPORT 548 | #endif 549 | 550 | extern "C" DLLEXPORT double putchard(double X) { 551 | fputc((char)X, stderr); 552 | return 0; 553 | } 554 | 555 | extern "C" DLLEXPORT double printd(double X) { 556 | fprintf(stderr, "%f\n", X); 557 | return 0; 558 | } 559 | 560 | int main() { 561 | InitializeNativeTarget(); 562 | InitializeNativeTargetAsmPrinter(); 563 | InitializeNativeTargetAsmParser(); 564 | 565 | BinopPrecedence['<'] = 10; 566 | BinopPrecedence['+'] = 20; 567 | BinopPrecedence['-'] = 20; 568 | BinopPrecedence['*'] = 40; 569 | 570 | fprintf(stderr, "ready> "); 571 | getNextToken(); 572 | 573 | TheJIT = ExitOnErr(KaleidoscopeJIT::Create()); 574 | 575 | InitializeModuleAndPassManager(); 576 | 577 | MainLoop(); 578 | 579 | TheModule->print(errs(), nullptr); 580 | 581 | return 0; 582 | } 583 | -------------------------------------------------------------------------------- /doc/Kaleidoscope02.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:实现解析器和AST 2 | 3 | ## 第二章绪论 4 | 5 | 本章将向您展示如何使用[第1章](Kaleidoscope01.md)中内置的词法分析器为我们的Kaleidoscope语言构建一个完整的[parser](http://en.wikipedia.org/wiki/Parsing)。一旦有了解析器,将定义并构建一个[抽象语法树](http://en.wikipedia.org/wiki/Abstract_syntax_tree)(AST)]。 6 | 7 | 我们将构建的解析器结合使用[递归下降Parsing](http://en.wikipedia.org/wiki/Recursive_descent_parser)]和[运算符优先Parsing](http://en.wikipedia.org/wiki/Operator-precedence_parser)]来解析Kaleidoscope语言(后者用于二进制表达式,前者用于其他所有内容)。在我们开始解析之前,让我们先谈谈解析器的输出:抽象语法树。 8 | 9 | ## 抽象语法树(AST) 10 | 11 | 程序的AST捕捉了程序行为,以便编译器后期阶段(例如代码生成)进行解释。基本上,我们希望语言中的每个构造(construct)都有一个对象,并且AST应该紧密地对语言进行建模。在Kaleidoscope中,我们有表达式、原型和函数对象。我们先从表达式开始: 12 | 13 | ```c++ 14 | /// ExprAST - Base class for all expression nodes. 15 | class ExprAST { 16 | public: 17 | virtual ~ExprAST() {} 18 | }; 19 | 20 | /// NumberExprAST - Expression class for numeric literals like "1.0". 21 | class NumberExprAST : public ExprAST { 22 | double Val; 23 | 24 | public: 25 | NumberExprAST(double Val) : Val(Val) {} 26 | }; 27 | ``` 28 | 29 | 上面的代码显示了ExprAST基类和一个用于数字文本的子类的定义。关于此代码需要注意的重要一点是,NumberExprAST类将文字的数值捕获为实例变量。这允许编译器的后续阶段知道存储的数值是什么。 30 | 31 | 现在我们只创建AST,所以没有创建有用的访问方法。例如,可以很容易地添加一个虚拟方法来漂亮地打印代码。下面是我们将在Kaleidoscope语言的基本形式中使用的其他表达式AST节点定义: 32 | 33 | ```c++ 34 | /// VariableExprAST - Expression class for referencing a variable, like "a". 35 | class VariableExprAST : public ExprAST { 36 | std::string Name; 37 | 38 | public: 39 | VariableExprAST(const std::string &Name) : Name(Name) {} 40 | }; 41 | 42 | /// BinaryExprAST - Expression class for a binary operator. 43 | class BinaryExprAST : public ExprAST { 44 | char Op; 45 | std::unique_ptr LHS, RHS; 46 | 47 | public: 48 | BinaryExprAST(char op, std::unique_ptr LHS, 49 | std::unique_ptr RHS) 50 | : Op(op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 51 | }; 52 | 53 | /// CallExprAST - Expression class for function calls. 54 | class CallExprAST : public ExprAST { 55 | std::string Callee; 56 | std::vector> Args; 57 | 58 | public: 59 | CallExprAST(const std::string &Callee, 60 | std::vector> Args) 61 | : Callee(Callee), Args(std::move(Args)) {} 62 | }; 63 | ``` 64 | 65 | 这一切(有意地)相当直观:变量捕获变量名,二元操作符捕获它们的操作码(例如,‘+’),调用捕获函数名以及任何参数表达式的列表。我们的AST有一点很好,那就是它捕获了语言特性,而不涉及语言的语法。请注意,这里没有讨论二元运算符的优先级、词汇结构等。 66 | 67 | 对于基础语言,这些都是我们将要定义的表达式节点。因为它没有条件控制流,所以它不是图灵完备的;我们将在后面的文章中修复这一点。接下来我们需要的两件事是一种表示函数接口的方式,以及一种表示函数本身的方式: 68 | 69 | ```c++ 70 | /// PrototypeAST - This class represents the "prototype" for a function, 71 | /// which captures its name, and its argument names (thus implicitly the number 72 | /// of arguments the function takes). 73 | class PrototypeAST { 74 | std::string Name; 75 | std::vector Args; 76 | 77 | public: 78 | PrototypeAST(const std::string &name, std::vector Args) 79 | : Name(name), Args(std::move(Args)) {} 80 | 81 | const std::string &getName() const { return Name; } 82 | }; 83 | 84 | /// FunctionAST - This class represents a function definition itself. 85 | class FunctionAST { 86 | std::unique_ptr Proto; 87 | std::unique_ptr Body; 88 | 89 | public: 90 | FunctionAST(std::unique_ptr Proto, 91 | std::unique_ptr Body) 92 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 93 | }; 94 | ``` 95 | 96 | 在Kaleidoscope中,函数的类型化只需对其参数进行计数。因为所有的值都是双精度浮点数,所以每个参数的类型不需要存储在任何地方。在更激进、更现实的语言中,“ExprAST”类可能会有一个类型字段。 97 | 98 | 有了这个脚手架,我们现在可以讨论在Kaleidoscope中解析表达式和函数体。 99 | 100 | ## 解析器基础 101 | 102 | 现在我们有一个AST要构建,我们需要定义解析器代码来构建它。这里的想法是,我们希望将类似“x+y”的内容(由词法分析器返回为三个令牌)解析为一个AST,该AST可以通过如下调用生成: 103 | 104 | ```c++ 105 | auto LHS = std::make_unique("x"); 106 | auto RHS = std::make_unique("y"); 107 | auto Result = std::make_unique('+', std::move(LHS), 108 | std::move(RHS)); 109 | ``` 110 | 111 | 为了做到这一点,我们将从定义一些基本的辅助例程开始: 112 | 113 | ```c++ 114 | /// CurTok/getNextToken - Provide a simple token buffer. CurTok is the current 115 | /// token the parser is looking at. getNextToken reads another token from the 116 | /// lexer and updates CurTok with its results. 117 | static int CurTok; 118 | static int getNextToken() { 119 | return CurTok = gettok(); 120 | } 121 | ``` 122 | 123 | 这在词法分析器周围实现了一个简单的令牌缓冲区。这允许我们提前查看词法分析器返回的内容。我们解析器中的每个函数都假定CurTok是需要解析的当前令牌。 124 | 125 | ```c++ 126 | /// LogError* - These are little helper functions for error handling. 127 | std::unique_ptr LogError(const char *Str) { 128 | fprintf(stderr, "LogError: %s\n", Str); 129 | return nullptr; 130 | } 131 | std::unique_ptr LogErrorP(const char *Str) { 132 | LogError(Str); 133 | return nullptr; 134 | } 135 | ``` 136 | 137 | `LogError`例程是简单的辅助例程,我们的解析器将使用它来处理错误。我们的解析器中的错误恢复不会是最好的,也不是特别用户友好的,但是对于我们的教程来说已经足够了。这些例程可以更容易地处理具有各种返回类型的例程中的错误:它们总是返回NULL。 138 | 139 | 有了这些基本的帮助器函数,我们就可以实现语法的第一部分:数字文本。 140 | 141 | ## 基本表达式解析 142 | 143 | 我们从数字常量开始,因为它们是最容易处理的。对于语法中的每个产生式,我们将定义一个函数来解析该产生式(production)。对于数字常量,我们有: 144 | 145 | ```c++ 146 | /// numberexpr ::= number 147 | static std::unique_ptr ParseNumberExpr() { 148 | auto Result = std::make_unique(NumVal); 149 | getNextToken(); // consume the number 150 | return std::move(Result); 151 | } 152 | ``` 153 | 154 | 此例程非常简单:它预期在当前令牌为`tok_number`令牌时被调用。它接受当前的数字值,创建一个`NumberExprAST‘节点,将词法分析器前进到下一个令牌,最后返回。 155 | 156 | 这其中有一些有趣的方面。最重要的一点是,该例程会吃掉与源码相对应的所有标记,并返回词法分析器缓冲区,其中下一个标记(不是语法产生式的一部分)已准备就绪。对于递归下降解析器来说,这是一种相当标准的方式。举个更好的例子,圆括号运算符定义如下: 157 | 158 | ```c++ 159 | /// parenexpr ::= '(' expression ')' 160 | static std::unique_ptr ParseParenExpr() { 161 | getNextToken(); // eat (. 162 | auto V = ParseExpression(); 163 | if (!V) 164 | return nullptr; 165 | 166 | if (CurTok != ')') 167 | return LogError("expected ')'"); 168 | getNextToken(); // eat ). 169 | return V; 170 | } 171 | ``` 172 | 173 | 此函数说明了有关解析器的许多有趣的事情: 174 | 175 | 1)它显示了我们如何使用LogError例程。调用此函数时,该函数期望当前令牌是一个‘(’令牌,但在解析子表达式之后,可能没有‘)’在等待。例如,如果用户键入“(4x”而不是“(4)”),解析器应该会发出错误。因为错误可能会发生,所以解析器需要一种方式来指示它们已经发生:在我们的解析器中,我们对错误返回NULL。 176 | 177 | 2)此函数的另一个有趣之处在于,它通过调用`ParseExpression`使用递归(我们很快就会看到`ParseExpression`可以调用`ParseParenExpr`)。这是非常强大的,因为它允许我们处理递归语法,并使每个产生式都非常简单。请注意,括号本身不会导致构造AST节点。虽然我们可以这样做,但是圆括号最重要的作用是引导解析器并提供分组。一旦解析器构造了AST,就不需要括号了。 178 | 179 | 下一个简单的例程用于处理变量引用和函数调用: 180 | 181 | ```c++ 182 | /// identifierexpr 183 | /// ::= identifier 184 | /// ::= identifier '(' expression* ')' 185 | static std::unique_ptr ParseIdentifierExpr() { 186 | std::string IdName = IdentifierStr; 187 | 188 | getNextToken(); // eat identifier. 189 | 190 | if (CurTok != '(') // Simple variable ref. 191 | return std::make_unique(IdName); 192 | 193 | // Call. 194 | getNextToken(); // eat ( 195 | std::vector> Args; 196 | if (CurTok != ')') { 197 | while (1) { 198 | if (auto Arg = ParseExpression()) 199 | Args.push_back(std::move(Arg)); 200 | else 201 | return nullptr; 202 | 203 | if (CurTok == ')') 204 | break; 205 | 206 | if (CurTok != ',') 207 | return LogError("Expected ')' or ',' in argument list"); 208 | getNextToken(); 209 | } 210 | } 211 | 212 | // Eat the ')'. 213 | getNextToken(); 214 | 215 | return std::make_unique(IdName, std::move(Args)); 216 | } 217 | ``` 218 | 219 | 此例程遵循与其他例程相同的样式。(如果当前Token是`tok_Identifier`令牌,则预期会被调用)。它还具有递归和错误处理功能。其中一个有趣的方面是,它使用*前瞻(look ahead)*来确定当前标识符是独立变量引用还是函数调用表达式。它通过检查标识符之后的令牌是否是‘(’令牌来处理此问题,根据需要构造`VariableExprAST`或`CallExprAST`节点。 220 | 221 | 现在我们已经准备好了所有简单的表达式解析逻辑,我们可以定义一个辅助函数来将其包装到一个入口点中。我们将这类表达式称为“主(Primary)”表达式,原因在[后续第6章教程](Kaleidoscope06.md)将变得更加清楚.为了解析任意主表达式,我们需要确定它是哪种表达式: 222 | 223 | ```c++ 224 | /// primary 225 | /// ::= identifierexpr 226 | /// ::= numberexpr 227 | /// ::= parenexpr 228 | static std::unique_ptr ParsePrimary() { 229 | switch (CurTok) { 230 | default: 231 | return LogError("unknown token when expecting an expression"); 232 | case tok_identifier: 233 | return ParseIdentifierExpr(); 234 | case tok_number: 235 | return ParseNumberExpr(); 236 | case '(': 237 | return ParseParenExpr(); 238 | } 239 | } 240 | ``` 241 | 242 | 现在您已经看到了该函数的定义,我们可以在各种函数中假定CurTok状态的原因就更加明显了。这使用前瞻来确定正在检查哪种类型的表达式,然后使用函数调用对其进行解析。 243 | 244 | 现在已经处理了基本表达式,我们需要处理二元表达式。它们稍微复杂一些。 245 | 246 | ## 二元表达式解析 247 | 248 | 二元表达式很难解析,因为它们通常是模棱两可的。例如,当给定字符串“x+y\*z”时,解析器可以选择将其解析为“(x+y)\*z”或“x+(y\*z)”。对于来自数学的通用定义,我们期待后面的解析,因为“\*”(乘法)的*优先顺序*高于“+”(加法)。 249 | 250 | 处理这一问题的方法有很多,但一种优雅而有效的方法是使用[操作符优先顺序解析](Operator-Prirecedence Parsing](http://en.wikipedia.org/wiki/Operator-precedence_parser).此解析技术使用二元运算符的优先级来指导递归。首先,我们需要一个优先顺序表: 251 | 252 | ```c++ 253 | /// BinopPrecedence - This holds the precedence for each binary operator that is 254 | /// defined. 255 | static std::map BinopPrecedence; 256 | 257 | /// GetTokPrecedence - Get the precedence of the pending binary operator token. 258 | static int GetTokPrecedence() { 259 | if (!isascii(CurTok)) 260 | return -1; 261 | 262 | // Make sure it's a declared binop. 263 | int TokPrec = BinopPrecedence[CurTok]; 264 | if (TokPrec <= 0) return -1; 265 | return TokPrec; 266 | } 267 | 268 | int main() { 269 | // Install standard binary operators. 270 | // 1 is lowest precedence. 271 | BinopPrecedence['<'] = 10; 272 | BinopPrecedence['+'] = 20; 273 | BinopPrecedence['-'] = 20; 274 | BinopPrecedence['*'] = 40; // highest. 275 | ... 276 | } 277 | ``` 278 | 279 | 对于Kaleidoscope的基本形式,我们将只支持4个二元运算符(这显然可以由您,我们勇敢无畏的读者来扩展)。`GetTokPrecedence`函数返回当前令牌的优先级,如果令牌不是二元运算符,则返回-1。有一个map可以方便地添加新的运算符,并清楚地表明算法不依赖于涉及的特定运算符,并且消除map并在`GetTokPrecedence`函数中进行比较也足够容易(或者只使用固定大小的数组)。 280 | 281 | 有了上面定义的辅助函数,我们现在可以开始解析二元表达式了。运算符优先解析的基本思想是将具有潜在歧义二元运算符的表达式分解为多个片段。例如,考虑表达式“a+b+(c+d)\*e\*f+g”。运算符优先解析将其视为由二元运算符分隔的主表达式流。因此,它将首先解析前导主表达式“a”,然后将看到对[+,b][+,(c+d)][\*,e][\*,f]和[+,g]。注意,因为括号是主表达式,所以二元表达式解析器根本不需要担心像(c+d)这样的嵌套子表达式。 282 | 283 | 首先,表达式可能是后面跟了一系列[binop,primary yexpr]对的主表达式: 284 | 285 | ```c++ 286 | /// expression 287 | /// ::= primary binoprhs 288 | /// 289 | static std::unique_ptr ParseExpression() { 290 | auto LHS = ParsePrimary(); 291 | if (!LHS) 292 | return nullptr; 293 | 294 | return ParseBinOpRHS(0, std::move(LHS)); 295 | } 296 | ``` 297 | 298 | `ParseBinOpRHS`是为我们解析成对序列的函数。它具有优先级和指向到目前为止已解析的部分的表达式的指针。请注意,“x”是一个完全有效的表达式:因此,允许“binoprhs”为空,在这种情况下,它返回传递给它的表达式。在上面的示例中,代码将“a”的表达式传递给`ParseBinOpRHS`,当前令牌为“+”。 299 | 300 | 传入`ParseBinOpRHS`的优先级值表示函数可以吃的*最小算子优先级*。例如,如果当前对流为[+,x],且`ParseBinOpRHS`的优先级为40,则不会消耗任何Token(因为‘+’的优先级仅为20)。考虑到这一点,`ParseBinOpRHS`以下述代码开始: 301 | 302 | ```c++ 303 | /// binoprhs 304 | /// ::= ('+' primary)* 305 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 306 | std::unique_ptr LHS) { 307 | // If this is a binop, find its precedence. 308 | while (1) { 309 | int TokPrec = GetTokPrecedence(); 310 | 311 | // If this is a binop that binds at least as tightly as the current binop, 312 | // consume it, otherwise we are done. 313 | if (TokPrec < ExprPrec) 314 | return LHS; 315 | ``` 316 | 317 | 此代码获取当前令牌的优先级,并检查是否太低。因为我们定义了优先级为-1的无效令牌,所以此检查隐含地知道当令牌流用完二元运算符时,对流结束。如果检查成功,我们就知道该令牌是二元运算符,并且它将包含在以下表达式中: 318 | 319 | ```c++ 320 | // Okay, we know this is a binop. 321 | int BinOp = CurTok; 322 | getNextToken(); // eat binop 323 | 324 | // Parse the primary expression after the binary operator. 325 | auto RHS = ParsePrimary(); 326 | if (!RHS) 327 | return nullptr; 328 | ``` 329 | 330 | 因此,此代码吃掉(并记住)二元运算符,然后解析后面的主表达式。这将构建整个对,对于运行的示例,第一个对是[+,b]。 331 | 332 | 现在我们已经解析了表达式的左侧和一对RHS序列,我们必须确定表达式关联的方式。特别地,我们可以使用“(a+b)binop unparsed”或“a+(B Binop Unparsed)”。为了确定这一点,我们向前看“binop”以确定其优先级,并将其与BinOp的优先级(在本例中为‘+’)进行比较: 333 | 334 | ```c++ 335 | // If BinOp binds less tightly with RHS than the operator after RHS, let 336 | // the pending operator take RHS as its LHS. 337 | int NextPrec = GetTokPrecedence(); 338 | if (TokPrec < NextPrec) { 339 | ``` 340 | 341 | 如果“rhs”右侧的binop的优先级低于或等于当前操作符的优先级,那么我们知道圆括号关联为“(a+b)binop.”。在我们的示例中,当前操作符是“+”,下一个操作符是“+”,我们知道它们具有相同的优先级。在本例中,我们将为“a+b”创建AST节点,然后继续解析: 342 | 343 | ```c++ 344 | ... if body omitted ... 345 | } 346 | 347 | // Merge LHS/RHS. 348 | LHS = std::make_unique(BinOp, std::move(LHS), 349 | std::move(RHS)); 350 | } // loop around to the top of the while loop. 351 | } 352 | ``` 353 | 354 | 在上面的示例中,这将把“a+b+”转换为“(a+b)”,并执行循环的下一次迭代,当前令牌为“+”。上面的代码将吃掉、记住并解析“(c+d)”作为主要表达式,这使得当前对等于[+,(c+d)]。然后,它将计算上面的‘if’条件,并将“\*”作为主数据库右侧的binop。在这种情况下,优先级“\*”高于优先级“+”,因此将输入IF条件。 355 | 356 | 这里留下的关键问题是“if条件如何完全解析右侧”?特别是,要为我们的示例正确构建AST,它需要获取所有“(c+d)\*e\*f”作为RHS表达式变量。执行此操作的代码出奇地简单(以上两个块中的代码在上下文中重复): 357 | 358 | ```c++ 359 | // If BinOp binds less tightly with RHS than the operator after RHS, let 360 | // the pending operator take RHS as its LHS. 361 | int NextPrec = GetTokPrecedence(); 362 | if (TokPrec < NextPrec) { 363 | RHS = ParseBinOpRHS(TokPrec+1, std::move(RHS)); 364 | if (!RHS) 365 | return nullptr; 366 | } 367 | // Merge LHS/RHS. 368 | LHS = std::make_unique(BinOp, std::move(LHS), 369 | std::move(RHS)); 370 | } // loop around to the top of the while loop. 371 | } 372 | ``` 373 | 374 | 此时,我们知道PRIMARY的RHS的二元运算符比我们当前正在解析的binop具有更高的优先级。因此,我们知道运算符的优先级都高于“+”的任何对序列都应该一起解析并返回为“RHS”。为此,我们递归调用`ParseBinOpRHS`函数,将“TokPrec+1”指定为继续执行所需的最低优先级。在上面的示例中,这将导致它返回“(c+d)\*e\*f”的AST节点作为RHS,然后将其设置为‘+’表达式的RHS。 375 | 376 | 最后,在While循环的下一次迭代中,将解析“+g”片段并将其添加到AST。通过这一小段代码(14行),我们以非常优雅的方式正确地处理了完全通用的二进制表达式解析。这是这段代码的快速浏览,有点微妙。我推荐用几个难理解的例子来看看它是如何工作的。 377 | 378 | 这就结束了表达式的处理。此时,我们可以将解析器指向任意令牌流,并从它构建表达式,在不属于表达式的第一个令牌处停止。接下来,我们需要处理函数定义等。 379 | 380 | ## 解析剩余部分 381 | 382 | 接下来缺少的是函数原型的处理。在Kaleidoscope中,它们既用于‘extern’函数声明,也用于函数体定义。执行此操作的代码简单明了,并且不是很有趣(一旦您从表达式中幸存下来): 383 | 384 | ```c++ 385 | /// prototype 386 | /// ::= id '(' id* ')' 387 | static std::unique_ptr ParsePrototype() { 388 | if (CurTok != tok_identifier) 389 | return LogErrorP("Expected function name in prototype"); 390 | 391 | std::string FnName = IdentifierStr; 392 | getNextToken(); 393 | 394 | if (CurTok != '(') 395 | return LogErrorP("Expected '(' in prototype"); 396 | 397 | // Read the list of argument names. 398 | std::vector ArgNames; 399 | while (getNextToken() == tok_identifier) 400 | ArgNames.push_back(IdentifierStr); 401 | if (CurTok != ')') 402 | return LogErrorP("Expected ')' in prototype"); 403 | 404 | // success. 405 | getNextToken(); // eat ')'. 406 | 407 | return std::make_unique(FnName, std::move(ArgNames)); 408 | } 409 | ``` 410 | 411 | 有了上述代码,解析函数定义非常简单,只需一个原型加上一个表达式来实现: 412 | 413 | ```c++ 414 | /// definition ::= 'def' prototype expression 415 | static std::unique_ptr ParseDefinition() { 416 | getNextToken(); // eat def. 417 | auto Proto = ParsePrototype(); 418 | if (!Proto) return nullptr; 419 | 420 | if (auto E = ParseExpression()) 421 | return std::make_unique(std::move(Proto), std::move(E)); 422 | return nullptr; 423 | } 424 | ``` 425 | 426 | 此外,我们还支持‘extern’声明函数,如‘sin’和‘cos’,并支持用户函数的正向声明。这些“extern”只是没有主体的原型: 427 | 428 | ```c++ 429 | /// external ::= 'extern' prototype 430 | static std::unique_ptr ParseExtern() { 431 | getNextToken(); // eat extern. 432 | return ParsePrototype(); 433 | } 434 | ``` 435 | 436 | 最后,我们还将允许用户键入任意顶层表达式并动态(译者注:原文为[on the fly](https://en.wikipedia.org/wiki/On_the_fly#Computer_usage))对其求值。我们将通过为其定义匿名空(零参数)函数来处理此问题: 437 | 438 | ```c++ 439 | /// toplevelexpr ::= expression 440 | static std::unique_ptr ParseTopLevelExpr() { 441 | if (auto E = ParseExpression()) { 442 | // Make an anonymous proto. 443 | auto Proto = std::make_unique("", std::vector()); 444 | return std::make_unique(std::move(Proto), std::move(E)); 445 | } 446 | return nullptr; 447 | } 448 | ``` 449 | 450 | 现在我们已经有了所有的部分,让我们构建一个小驱动程序,它将让我们真正*执行*我们已经构建的代码! 451 | 452 | ## 驱动 453 | 454 | 驱动程序只需使用顶层分派循环调用所有解析段。这里没有太多有趣的地方,所以我将只包含顶层循环。请参阅[下面](#完整代码列表)以获取“顶层解析”部分的完整代码。 455 | 456 | ```c++ 457 | /// top ::= definition | external | expression | ';' 458 | static void MainLoop() { 459 | while (1) { 460 | fprintf(stderr, "ready> "); 461 | switch (CurTok) { 462 | case tok_eof: 463 | return; 464 | case ';': // ignore top-level semicolons. 465 | getNextToken(); 466 | break; 467 | case tok_def: 468 | HandleDefinition(); 469 | break; 470 | case tok_extern: 471 | HandleExtern(); 472 | break; 473 | default: 474 | HandleTopLevelExpression(); 475 | break; 476 | } 477 | } 478 | } 479 | ``` 480 | 481 | 其中最有趣的部分是我们忽略了顶层分号。你会问,为什么会这样?基本原因是,如果您在命令行键入“4+5”,解析器不知道您要键入的内容是否结束。例如,您可以在下一行键入“def Foo.”,在这种情况下,4+5是顶层表达式的末尾。或者,您也可以键入“\*6”,这将继续表达式。拥有顶层分号解析允许您键入“4+5;”,解析器可以理解您的行为。 482 | 483 | ## 结论 484 | 485 | 用不到400行注释代码(240行非注释、非空白代码),我们完全定义了我们的最小语言,包括词法分析器、解析器和AST构建器。完成此操作后,可执行文件将验证Kaleidoscope代码,并告诉我们它在语法上是否无效。例如,下面是一个交互示例: 486 | 487 | ```bash 488 | $ ./a.out 489 | ready> def foo(x y) x+foo(y, 4.0); 490 | Parsed a function definition. 491 | ready> def foo(x y) x+y y; 492 | Parsed a function definition. 493 | Parsed a top-level expr 494 | ready> def foo(x y) x+y ); 495 | Parsed a function definition. 496 | Error: unknown token when expecting an expression 497 | ready> extern sin(a); 498 | ready> Parsed an extern 499 | ready> ^D 500 | $ 501 | ``` 502 | 503 | 这里有很大的扩展空间。您可以定义新的AST节点,以多种方式扩展语言等。在[下一篇](Kaleidoscope03.md)中,我们将介绍如何从AST生成LLVM中间表示(IR)。 504 | 505 | 506 | [下一步:实现LLVM IR代码生成](Kaleidoscope03.md) 507 | 508 | 509 | ## 后记:心得体会 510 | 1. 抽象语法树(AST)是对语言建模的结果,这里AST分为表达式,原型(protoType)和函数三大类; 511 | 2. 语法解析的过程就是将Token构建为抽象语法树的过程; 512 | 3. 解析过程采用递归下降解析和运算符优先解析。 -------------------------------------------------------------------------------- /doc/Kaleidoscope05.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:扩展语言:控制流 2 | 3 | ## 第五章绪论 4 | 5 | 第1-4部分描述了简单Kaleidoscope语言的实现,包括对生成LLVM IR的支持,随后是优化和JIT编译器。不幸的是,正如所展示的那样,Kaleidoscope几乎毫无用处:除了调用和返回之外,它没有任何控制流。这意味着你在代码中不能有条件分支,这大大限制了它的功能。在“构建编译器”的这一集中,我们将扩展Kaleidoscope,使其有一个if/Then/Else表达式和一个简单的‘for’循环。 6 | 7 | ## IF/THEN/ELSE 8 | 9 | 扩展Kaleidoscope以支持IF/THEN/ELSE非常简单。它基本上需要向词法分析器、解析器、AST和LLVM代码发射器添加对这个“新”概念的支持。这个例子很不错,因为它展示了随着时间的推移“扩展”一门语言是多么容易,随着新思想的发现而逐渐扩展。 10 | 11 | 在我们继续“如何”添加此扩展之前,让我们先来讨论一下我们想要什么。基本思想是我们希望能够编写这样的东西: 12 | ``` 13 | def fib(x) 14 | if x < 3 then 15 | 1 16 | else 17 | fib(x-1)+fib(x-2); 18 | ``` 19 | 20 | 在Kaleidoscope中,每个结构都是一个表达式(expression):没有语句(statement)。因此,IF/THEN/ELSE表达式需要像其他表达式一样返回值。因为我们使用的主要是函数形式,所以我们将让它评估其条件,然后根据条件的解决方式返回‘THEN’或‘ELSE’值。这与C“?:”表达式非常相似。 21 | 22 | IF/THEN/ELSE表达式的语义是它将条件计算为布尔相等的值:0.0被认为是假的,而其他一切都被认为是真的。如果条件为TRUE,则计算并返回第一个子表达式;如果条件为FALSE,则计算并返回第二个子表达式。因为Kaleidoscope允许[side effect](https://en.wikipedia.org/wiki/Side_effect_(computer_science)#:~:text=In%20computer%20science%2C%20an%20operation,the%20invoker%20of%20the%20operation.),所以这一行为对于确定分支是很重要的。 23 | 24 | 既然我们知道了我们“想要”什么,让我们把它分解成几个组成部分。 25 | 26 | ### IF/THEN/ELSE的词法分析器扩展 27 | 28 | 词法分析器扩展很简单。首先,我们为相关令牌添加新的枚举值: 29 | 30 | ```c++ 31 | // control 32 | tok_if = -6, 33 | tok_then = -7, 34 | tok_else = -8, 35 | ``` 36 | 37 | 一旦我们有了它,我们就可以识别词法分析器中的新关键字。这是非常简单的东西: 38 | 39 | ```c++ 40 | ... 41 | if (IdentifierStr == "def") 42 | return tok_def; 43 | if (IdentifierStr == "extern") 44 | return tok_extern; 45 | if (IdentifierStr == "if") 46 | return tok_if; 47 | if (IdentifierStr == "then") 48 | return tok_then; 49 | if (IdentifierStr == "else") 50 | return tok_else; 51 | return tok_identifier; 52 | ``` 53 | 54 | ### IF/THEN/ELSE的AST扩展 55 | 56 | 为了表示新表达式,我们为其添加一个新的AST节点: 57 | 58 | ```c++ 59 | /// IfExprAST - Expression class for if/then/else. 60 | class IfExprAST : public ExprAST { 61 | std::unique_ptr Cond, Then, Else; 62 | 63 | public: 64 | IfExprAST(std::unique_ptr Cond, std::unique_ptr Then, 65 | std::unique_ptr Else) 66 | : Cond(std::move(Cond)), Then(std::move(Then)), Else(std::move(Else)) {} 67 | 68 | Value *codegen() override; 69 | }; 70 | ``` 71 | 72 | AST节点只有指向各种子表达式的指针。 73 | 74 | ### IF/THEN/ELSE的解析器扩展 75 | 76 | 既然我们有了来自词法分析器的相关令牌,也有了要构建的AST节点,我们的解析逻辑就相对简单了。首先,我们定义一个新的解析函数: 77 | 78 | ```c++ 79 | /// ifexpr ::= 'if' expression 'then' expression 'else' expression 80 | static std::unique_ptr ParseIfExpr() { 81 | getNextToken(); // eat the if. 82 | 83 | // condition. 84 | auto Cond = ParseExpression(); 85 | if (!Cond) 86 | return nullptr; 87 | 88 | if (CurTok != tok_then) 89 | return LogError("expected then"); 90 | getNextToken(); // eat the then 91 | 92 | auto Then = ParseExpression(); 93 | if (!Then) 94 | return nullptr; 95 | 96 | if (CurTok != tok_else) 97 | return LogError("expected else"); 98 | 99 | getNextToken(); 100 | 101 | auto Else = ParseExpression(); 102 | if (!Else) 103 | return nullptr; 104 | 105 | return std::make_unique(std::move(Cond), std::move(Then), 106 | std::move(Else)); 107 | } 108 | ``` 109 | 110 | 接下来,我们将其作为主表达式连接起来: 111 | 112 | ```c++ 113 | static std::unique_ptr ParsePrimary() { 114 | switch (CurTok) { 115 | default: 116 | return LogError("unknown token when expecting an expression"); 117 | case tok_identifier: 118 | return ParseIdentifierExpr(); 119 | case tok_number: 120 | return ParseNumberExpr(); 121 | case '(': 122 | return ParseParenExpr(); 123 | case tok_if: 124 | return ParseIfExpr(); 125 | } 126 | } 127 | ``` 128 | 129 | ### IF/THEN/ELSE的LLVM IR 130 | 131 | 现在我们已经有了解析和构建AST的功能,最后一部分是添加LLVM代码生成支持。这是IF/THEN/ELSE示例中最有趣的部分,因为这是引入新概念的开始。上面的所有代码都在前面的章节中进行了详细描述。 132 | 133 | 为了激发我们想要生成的代码,让我们来看一个简单的例子。考虑一下: 134 | ``` 135 | extern foo(); 136 | extern bar(); 137 | def baz(x) if x then foo() else bar(); 138 | ``` 139 | 140 | 如果禁用优化,您将(很快)从Kaleidoscope获得的代码如下所示: 141 | 142 | ```llvm 143 | declare double @foo() 144 | 145 | declare double @bar() 146 | 147 | define double @baz(double %x) { 148 | entry: 149 | %ifcond = fcmp one double %x, 0.000000e+00 150 | br i1 %ifcond, label %then, label %else 151 | 152 | then: ; preds = %entry 153 | %calltmp = call double @foo() 154 | br label %ifcont 155 | 156 | else: ; preds = %entry 157 | %calltmp1 = call double @bar() 158 | br label %ifcont 159 | 160 | ifcont: ; preds = %else, %then 161 | %iftmp = phi double [ %calltmp, %then ], [ %calltmp1, %else ] 162 | ret double %iftmp 163 | } 164 | ``` 165 | 166 | 要可视化控制流图,您可以使用LLVM[OPT](https://llvm.org/cmds/opt.html)工具的一个很好的特性。如果您将此LLVMIR放入“t.ll”并运行“`llvm-as < t.ll | OPT-ANALYLE-VIEW-Cfg`”,[将弹出一个窗口up](https://llvm.org/docs/ProgrammersManual.html#viewing-graphs-while-debugging-code),您将看到此图形: 167 | 168 | ![示例配置](pics/Kaleidoscope05.png){.ign-center} 169 | 170 | 实现这一点的另一种方法是调用“`F->viewCFG()`”或“`F->viewCFGOnly()`”(其中F是“`Function*`”),方法是将实际调用插入代码并重新编译,或者在调试器中调用它们。LLVM有许多用于可视化各种图形的很好的特性。 171 | 172 | 返回到生成的代码,它相当简单:entry block计算条件表达式(在我们的示例中是“x”),并用“`fcmp one`”指令(‘one’is“Ordered and Not Equity”)将结果与0.0进行比较。根据该表达式的结果,代码跳转到“THEN”或“ELSE”块,这两个块包含TRUE/FALSE情况的表达式。 173 | 174 | THEN/ELSE块执行完毕后,它们都会分支回‘ifcont’块,以执行IF/THEN/ELSE之后发生的代码。在这种情况下,剩下的唯一要做的事情就是返回到函数的调用方。然后问题就变成了:代码如何知道要返回哪个表达式? 175 | 176 | 这个问题的答案涉及到一个重要的SSA操作:[Phi operation](http://en.wikipedia.org/wiki/Static_single_assignment_form).如果你不熟悉ssa,[维基百科article](http://en.wikipedia.org/wiki/Static_single_assignment_form)是一个很好的介绍,在你最喜欢的搜索引擎上有各种各样的其他介绍。简而言之,“执行”φ操作需要“记住”哪个block控件是从何而来的。φ操作采用与input control block相对应的值。在本例中,如果控制权来自“THEN”block,它将获得“calltmp”的值。如果控制权来自“Else”block,则获取“calltmp1”的值。 177 | 178 | 在这一点上,您可能开始想“哦,不!这意味着我的简单而优雅的前端必须开始生成SSA表单才能使用LLVM!”幸运的是,情况并非如此,我们强烈建议*不*在您的前端实现SSA构建算法,除非有令人惊讶的好理由。实际上,在为一般命令式编程语言编写的代码中,有两种值待计算的值可能需要φ节点: 179 | 180 | 1. 涉及用户变量的代码:`x=1;x=x+1;` 181 | 2. 隐含在AST结构中的值,如在本例中为Phi节点。 182 | 183 | 在本教程(“可变变量”)的[第7章](Kaleidoscope07.md)中,我们将深入讨论#1。现在,请相信我,您不需要使用SSA构造来处理这种情况。对于#2,您可以选择使用我们将在#1中描述的技术,也可以在方便的情况下直接插入Phi节点。在这种情况下,生成Phi节点非常容易,所以我们选择直接执行。 184 | 185 | 好了,动机和概述到此为止,让我们生成代码吧! 186 | 187 | ### IF/THEN/ELSE的代码生成 188 | 189 | 为了生成代码,我们为`IfExprAST`实现了`codegen`方法: 190 | 191 | ```c++ 192 | Value *IfExprAST::codegen() { 193 | Value *CondV = Cond->codegen(); 194 | if (!CondV) 195 | return nullptr; 196 | 197 | // Convert condition to a bool by comparing non-equal to 0.0. 198 | CondV = Builder.CreateFCmpONE( 199 | CondV, ConstantFP::get(TheContext, APFloat(0.0)), "ifcond"); 200 | ``` 201 | 202 | 这段代码简单明了,与我们之前看到的类似。我们发出该条件的表达式,然后将该值与零进行比较,以获得1位(布尔值)形式的真值。 203 | 204 | ```c++ 205 | Function *TheFunction = Builder.GetInsertBlock()->getParent(); 206 | 207 | // Create blocks for the then and else cases. Insert the 'then' block at the 208 | // end of the function. 209 | BasicBlock *ThenBB = 210 | BasicBlock::Create(TheContext, "then", TheFunction); 211 | BasicBlock *ElseBB = BasicBlock::Create(TheContext, "else"); 212 | BasicBlock *MergeBB = BasicBlock::Create(TheContext, "ifcont"); 213 | 214 | Builder.CreateCondBr(CondV, ThenBB, ElseBB); 215 | ``` 216 | 217 | 此代码创建与IF/THEN/ELSE语句相关的基本块,并直接对应于上面示例中的块。第一行获取正在构建的当前函数对象。它通过向构建器询问当前的BasicBlock,并向block询问它的“父节点”(它当前嵌入到其中的函数)来实现这一点。 218 | 219 | 一旦有了它,它就会创建三个块。注意,它将“TheFunction”传递给“THEN”block的构造函数。这会使构造函数自动将新block插入到指定函数的末尾。其他两个块已创建,但尚未插入到函数中。 220 | 221 | 一旦创建了块,我们就可以发出在它们之间进行选择的条件分支。请注意,创建新块不会隐式影响IRBuilder,因此它仍会插入到条件进入的block中。还要注意的是,它正在创建一个指向“THEN”block和“ELSE”block的分支,尽管“ELSE”block还没有插入到函数中。这一切都没问题:这是LLVM支持正向引用的标准方式。 222 | 223 | ```c++ 224 | // Emit then value. 225 | Builder.SetInsertPoint(ThenBB); 226 | 227 | Value *ThenV = Then->codegen(); 228 | if (!ThenV) 229 | return nullptr; 230 | 231 | Builder.CreateBr(MergeBB); 232 | // Codegen of 'Then' can change the current block, update ThenBB for the PHI. 233 | ThenBB = Builder.GetInsertBlock(); 234 | ``` 235 | 236 | 在插入条件分支之后,我们移动构建器以开始插入到“THEN”block中。严格地说,此调用将插入点移动到指定block的末尾。不过,由于“THEN”block是空的,所以也是从插入block开头开始的。:) 237 | 238 | 一旦设置了插入点,我们就从AST递归地编码生成“THEN”表达式。为了完成“THEN”block,我们创建一个无条件分支来合并block。LLVM IR的一个有趣(也是非常重要的)方面是,它要求所有基本块都使用一个[`控制流指令`](https://llvm.org/docs/LangRef.html#terminators)(如return或分支)“终止”。这意味着所有控制流*包括fall-through*必须在LLVMIR中显式显示。如果您违反此规则,验证器将发出错误。 239 | 240 | 这里的最后一行相当微妙,但非常重要。基本问题是,当我们在合并block中创建phi节点时,我们需要设置block/value对,以指示phi将如何工作。重要的是,phi节点希望在cfg中为block的每个前驱都有一个条目。那么,为什么我们刚刚将block设置为以上5行,就会得到当前的block呢?问题是,then block中可能实际上会修改生成器Builder发送到if中的block,比如then表达式中包含嵌套的“IF/THEN/ELSE”表达式。因为递归调用`codegen()`可能会任意改变当前block的概念,所以我们需要获取最新值,赋值给设置Phi节点的代码。 241 | 242 | ```c++ 243 | // Emit else block. 244 | TheFunction->getBasicBlockList().push_back(ElseBB); 245 | Builder.SetInsertPoint(ElseBB); 246 | 247 | Value *ElseV = Else->codegen(); 248 | if (!ElseV) 249 | return nullptr; 250 | 251 | Builder.CreateBr(MergeBB); 252 | // codegen of 'Else' can change the current block, update ElseBB for the PHI. 253 | ElseBB = Builder.GetInsertBlock(); 254 | ``` 255 | 256 | Elseblock的代码生成与then block的代码生成基本相同。唯一显著的区别是第一行,它将‘Else’block添加到函数中。回想一下,前面已经创建了‘Else’block,但没有添加到函数中。现在已经发出了‘THEN’和‘ELSE’块,我们可以完成合并代码: 257 | 258 | ```c++ 259 | // Emit merge block. 260 | TheFunction->getBasicBlockList().push_back(MergeBB); 261 | Builder.SetInsertPoint(MergeBB); 262 | PHINode *PN = 263 | Builder.CreatePHI(Type::getDoubleTy(TheContext), 2, "iftmp"); 264 | 265 | PN->addIncoming(ThenV, ThenBB); 266 | PN->addIncoming(ElseV, ElseBB); 267 | return PN; 268 | } 269 | ``` 270 | 271 | 这里的前两行现在很熟悉:第一行将“Merge”block添加到函数对象中(它以前是浮点的,就像上面的Elseblock一样)。第二个更改插入点,以便新创建的代码将进入“Merge”block。完成后,我们需要创建PHI节点并为PHI设置block/value对。 272 | 273 | 最后,CodeGen函数将phi节点作为IF/THEN/ELSE表达式计算的值返回。在上面的示例中,此返回值将提供给顶层函数的代码,该代码将创建返回指令。 274 | 275 | 总体而言,我们现在能够在Kaleidoscope中执行条件代码。有了这个扩展,Kaleidoscope是一种相当完整的语言,可以计算各种各样的数值函数。接下来,我们将添加另一个在非函数式语言中熟悉的有用表达式. 276 | 277 | ## ‘for’循环表达式 278 | 279 | 既然我们知道了如何将基本的控制流结构添加到语言中,我们就有了工具来添加更强大的东西。让我们添加一些更具攻击性的东西,‘for’表达式: 280 | ``` 281 | extern putchard(char); 282 | def printstar(n) 283 | for i = 1, i < n, 1.0 in 284 | putchard(42); # ascii 42 = '*' 285 | 286 | # print 100 '*' characters 287 | printstar(100); 288 | ``` 289 | 290 | 该表达式定义了一个从起始值迭代的新变量(在本例中为“i”),而条件(在本例中为“i < n”)为真,递增一个可选的步长值(在本例中为“1.0”)。如果省略步长值,则默认为1.0。当循环为真时,它执行其主体表达式。因为我们没有更好的返回,所以我们将循环定义为总是返回0.0。将来当我们有可变变量时,它会变得更有用。 291 | 292 | 像以前一样,让我们来讨论一下我们需要对Kaleidoscope进行哪些更改来支持这一点。 293 | 294 | ### ‘for’循环的词法分析器扩展 295 | 296 | 词法分析器扩展与IF/THEN/ELSE相同: 297 | 298 | ```c++ 299 | ... in enum Token ... 300 | // control 301 | tok_if = -6, tok_then = -7, tok_else = -8, 302 | tok_for = -9, tok_in = -10 303 | 304 | ... in gettok ... 305 | if (IdentifierStr == "def") 306 | return tok_def; 307 | if (IdentifierStr == "extern") 308 | return tok_extern; 309 | if (IdentifierStr == "if") 310 | return tok_if; 311 | if (IdentifierStr == "then") 312 | return tok_then; 313 | if (IdentifierStr == "else") 314 | return tok_else; 315 | if (IdentifierStr == "for") 316 | return tok_for; 317 | if (IdentifierStr == "in") 318 | return tok_in; 319 | return tok_identifier; 320 | ``` 321 | 322 | ### ‘for’循环的AST扩展 323 | 324 | AST节点也同样简单。它基本上归结为捕获节点中的变量名和组成表达式。 325 | 326 | ```c++ 327 | /// ForExprAST - Expression class for for/in. 328 | class ForExprAST : public ExprAST { 329 | std::string VarName; 330 | std::unique_ptr Start, End, Step, Body; 331 | 332 | public: 333 | ForExprAST(const std::string &VarName, std::unique_ptr Start, 334 | std::unique_ptr End, std::unique_ptr Step, 335 | std::unique_ptr Body) 336 | : VarName(VarName), Start(std::move(Start)), End(std::move(End)), 337 | Step(std::move(Step)), Body(std::move(Body)) {} 338 | 339 | Value *codegen() override; 340 | }; 341 | ``` 342 | 343 | ### ‘for’循环的解析器扩展 344 | 345 | 解析器代码也相当标准。这里唯一有趣的事情是处理可选的步长值。解析器代码通过检查第二个逗号是否存在来处理它。如果不是,则在AST节点中将步长值设置为NULL: 346 | 347 | ```c++ 348 | /// forexpr ::= 'for' identifier '=' expr ',' expr (',' expr)? 'in' expression 349 | static std::unique_ptr ParseForExpr() { 350 | getNextToken(); // eat the for. 351 | 352 | if (CurTok != tok_identifier) 353 | return LogError("expected identifier after for"); 354 | 355 | std::string IdName = IdentifierStr; 356 | getNextToken(); // eat identifier. 357 | 358 | if (CurTok != '=') 359 | return LogError("expected '=' after for"); 360 | getNextToken(); // eat '='. 361 | 362 | 363 | auto Start = ParseExpression(); 364 | if (!Start) 365 | return nullptr; 366 | if (CurTok != ',') 367 | return LogError("expected ',' after for start value"); 368 | getNextToken(); 369 | 370 | auto End = ParseExpression(); 371 | if (!End) 372 | return nullptr; 373 | 374 | // The step value is optional. 375 | std::unique_ptr Step; 376 | if (CurTok == ',') { 377 | getNextToken(); 378 | Step = ParseExpression(); 379 | if (!Step) 380 | return nullptr; 381 | } 382 | 383 | if (CurTok != tok_in) 384 | return LogError("expected 'in' after for"); 385 | getNextToken(); // eat 'in'. 386 | 387 | auto Body = ParseExpression(); 388 | if (!Body) 389 | return nullptr; 390 | 391 | return std::make_unique(IdName, std::move(Start), 392 | std::move(End), std::move(Step), 393 | std::move(Body)); 394 | } 395 | ``` 396 | 397 | 我们再一次把它作为一个主要的表达式: 398 | 399 | ```c++ 400 | static std::unique_ptr ParsePrimary() { 401 | switch (CurTok) { 402 | default: 403 | return LogError("unknown token when expecting an expression"); 404 | case tok_identifier: 405 | return ParseIdentifierExpr(); 406 | case tok_number: 407 | return ParseNumberExpr(); 408 | case '(': 409 | return ParseParenExpr(); 410 | case tok_if: 411 | return ParseIfExpr(); 412 | case tok_for: 413 | return ParseForExpr(); 414 | } 415 | } 416 | ``` 417 | 418 | ### ‘for’循环的LLVM IR 419 | 420 | 现在我们来看好的部分:我们想要为这件事生成的LLVM IR。通过上面的简单示例,我们将获得此LLVM IR(请注意,为清晰起见,生成此转储时禁用了优化): 421 | 422 | ```llvm 423 | declare double @putchard(double) 424 | 425 | define double @printstar(double %n) { 426 | entry: 427 | ; initial value = 1.0 (inlined into phi) 428 | br label %loop 429 | 430 | loop: ; preds = %loop, %entry 431 | %i = phi double [ 1.000000e+00, %entry ], [ %nextvar, %loop ] 432 | ; body 433 | %calltmp = call double @putchard(double 4.200000e+01) 434 | ; increment 435 | %nextvar = fadd double %i, 1.000000e+00 436 | 437 | ; termination test 438 | %cmptmp = fcmp ult double %i, %n 439 | %booltmp = uitofp i1 %cmptmp to double 440 | %loopcond = fcmp one double %booltmp, 0.000000e+00 441 | br i1 %loopcond, label %loop, label %afterloop 442 | 443 | afterloop: ; preds = %loop 444 | ; loop always returns 0.0 445 | ret double 0.000000e+00 446 | } 447 | ``` 448 | 449 | 这个循环包含我们以前看到的所有相同的结构:一个phi节点、几个表达式和一些基本块。让我们看看这两个组件是如何搭配在一起的。 450 | 451 | ### ‘for’循环的代码生成 452 | 453 | codegen的第一部分非常简单:我们只输出循环值的开始表达式: 454 | 455 | ```c++ 456 | Value *ForExprAST::codegen() { 457 | // Emit the start code first, without 'variable' in scope. 458 | Value *StartVal = Start->codegen(); 459 | if (!StartVal) 460 | return nullptr; 461 | ``` 462 | 463 | 这样就解决了问题,下一步是为循环体的开始设置LLVM Basicblock。在上面的例子中,整个循环体是一个block,但请记住,体代码本身可以由多个块组成(例如,如果它包含IF/THEN/ELSE或FOR/in表达式)。 464 | 465 | ```c++ 466 | // Make the new basic block for the loop header, inserting after current 467 | // block. 468 | Function *TheFunction = Builder.GetInsertBlock()->getParent(); 469 | BasicBlock *PreheaderBB = Builder.GetInsertBlock(); 470 | BasicBlock *LoopBB = 471 | BasicBlock::Create(TheContext, "loop", TheFunction); 472 | 473 | // Insert an explicit fall through from the current block to the LoopBB. 474 | Builder.CreateBr(LoopBB); 475 | ``` 476 | 477 | 此代码类似于我们在if/Then/Else中看到的代码。因为我们将需要它来创建Phi节点,所以我们记住了落入循环中的block。完成后,我们将创建实际的block来启动循环,并为两个块之间的fall-through创建无条件的分支。 478 | 479 | ```c++ 480 | // Start insertion in LoopBB. 481 | Builder.SetInsertPoint(LoopBB); 482 | 483 | // Start the PHI node with an entry for Start. 484 | PHINode *Variable = Builder.CreatePHI(Type::getDoubleTy(TheContext), 485 | 2, VarName.c_str()); 486 | Variable->addIncoming(StartVal, PreheaderBB); 487 | ``` 488 | 489 | 现在已经设置了循环的“preheader”,我们切换到为循环体发送代码。首先,我们移动插入点并为loop induction变量创建PHI节点。因为我们已经知道起始值的传入值,所以我们将其添加到phi节点。注意,phi最终将为backedge获得第二个值,但是我们还不能设置它(因为它不存在!)。 490 | 491 | ```c++ 492 | // Within the loop, the variable is defined equal to the PHI node. If it 493 | // shadows an existing variable, we have to restore it, so save it now. 494 | Value *OldVal = NamedValues[VarName]; 495 | NamedValues[VarName] = Variable; 496 | 497 | // Emit the body of the loop. This, like any other expr, can change the 498 | // current BB. Note that we ignore the value computed by the body, but don't 499 | // allow an error. 500 | if (!Body->codegen()) 501 | return nullptr; 502 | ``` 503 | 504 | 现在代码开始变得更有趣了。我们的‘for’循环在符号表中引入了一个新变量。这意味着我们的符号表现在可以包含函数参数或循环变量。为了处理这个问题,在我们对循环体进行编码之前,我们添加循环变量作为其名称的当前值。请注意,外部作用域中可能存在同名的变量。很容易将此设置为错误(如果已有VarName条目,则发出错误并返回NULL),但我们选择允许跟踪变量。为了正确处理这个问题,我们要记住在`OldVal`中可能隐藏的值(如果没有隐藏变量,则该值为NULL)。 505 | 506 | 一旦循环变量被设置到符号表中,代码递归地调用codegen。这允许主体使用循环变量:任何对它的引用都会自然地在符号表中找到它。 507 | 508 | ```c++ 509 | // Emit the step value. 510 | Value *StepVal = nullptr; 511 | if (Step) { 512 | StepVal = Step->codegen(); 513 | if (!StepVal) 514 | return nullptr; 515 | } else { 516 | // If not specified, use 1.0. 517 | StepVal = ConstantFP::get(TheContext, APFloat(1.0)); 518 | } 519 | 520 | Value *NextVar = Builder.CreateFAdd(Variable, StepVal, "nextvar"); 521 | ``` 522 | 523 | 既然主体已经发出(emit),我们将通过添加Step值来计算迭代变量的下一个值,如果不存在,则使用1.0。‘`NextVar`’将是循环下一次迭代的循环变量的值。 524 | 525 | ```c++ 526 | // Compute the end condition. 527 | Value *EndCond = End->codegen(); 528 | if (!EndCond) 529 | return nullptr; 530 | 531 | // Convert condition to a bool by comparing non-equal to 0.0. 532 | EndCond = Builder.CreateFCmpONE( 533 | EndCond, ConstantFP::get(TheContext, APFloat(0.0)), "loopcond"); 534 | ``` 535 | 536 | 最后,我们评估循环的退出值,以确定循环是否应该退出。这其实是IF/THEN/ELSE语句的条件求值的镜像。 537 | 538 | ```c++ 539 | // Create the "after loop" block and insert it. 540 | BasicBlock *LoopEndBB = Builder.GetInsertBlock(); 541 | BasicBlock *AfterBB = 542 | BasicBlock::Create(TheContext, "afterloop", TheFunction); 543 | 544 | // Insert the conditional branch into the end of LoopEndBB. 545 | Builder.CreateCondBr(EndCond, LoopBB, AfterBB); 546 | 547 | // Any new code will be inserted in AfterBB. 548 | Builder.SetInsertPoint(AfterBB); 549 | ``` 550 | 551 | 循环主体的代码完成后,我们只需要完成它的控制流。此代码记住结束的block(对于Phi节点),然后为循环出口创建block(“After Loop”)。根据退出条件的值,它创建一个条件分支,在再次执行循环和退出循环之间进行选择。将来的任何代码都会在“After Loop”block中发出,因此它会将插入位置设置为它。 552 | 553 | ```c++ 554 | // Add a new entry to the PHI node for the backedge. 555 | Variable->addIncoming(NextVar, LoopEndBB); 556 | 557 | // Restore the unshadowed variable. 558 | if (OldVal) 559 | NamedValues[VarName] = OldVal; 560 | else 561 | NamedValues.erase(VarName); 562 | 563 | // for expr always returns 0.0. 564 | return Constant::getNullValue(Type::getDoubleTy(TheContext)); 565 | } 566 | ``` 567 | 568 | 最后的代码处理各种清理:现在我们有了“NextVar”值,我们可以将传入的值添加到循环PHI节点。之后,我们从符号表中删除循环变量,以便它不在for循环之后的作用域内。最后,for循环的代码生成总是返回0.0,这就是我们从`ForExprAST::codegen()`返回的内容。 569 | 570 | 至此,我们结束了本教程的“向Kaleidoscope添加控制流”一章。在本章中,我们添加了两个控制流构造,并使用它们来激发LLVM IR的一些重要方面,这些方面对于前端实现者来说是非常重要的。在我们传奇的下一章中,我们将变得更加疯狂,将[用户定义操作符](Kaleidoscope06.md)添加到我们可怜又无辜的语言中。 571 | 572 | 573 | [下一步:扩展语言:自定义运算符](Kaleidoscope06.md) 574 | -------------------------------------------------------------------------------- /doc/Kaleidoscope07.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:扩展语言:可变变量 2 | 3 | ## 第七章简介 4 | 5 | 在第1章到第6章中,我们已经构建了一个非常值得尊敬的[函数式编程语言](http://en.wikipedia.org/wiki/Functional_programming).]。在我们的旅程中,我们学习了一些解析技术,如何构建和表示一个AST,如何构建LLVMIR,以及如何优化结果代码和即时编译它。 6 | 7 | 虽然Kaleidoscope作为一种函数式语言很有趣,但它是函数式的这一事实使得为它生成LLVMIR“太容易”了。特别是,函数式语言使得直接在[ssa form](http://en.wikipedia.org/wiki/Static_single_assignment_form)中构建LLVMIR变得非常容易由于LLVM要求输入代码采用SSA形式,这是一个非常好的属性,新手通常不清楚如何为具有可变变量的命令式语言生成代码。 8 | 9 | 本章的简短(令人愉快的)总结是,您的前端不需要构建SSA表单:LLVM为此提供了高度调优和经过良好测试的支持,尽管它的工作方式对某些人来说有点出乎意料。 10 | 11 | ## 为什么这是一个很难解决的问题? 12 | 13 | 要理解为什么可变变量会导致SSA构造的复杂性,请考虑下面这个极其简单的C示例: 14 | 15 | ```c 16 | int G, H; 17 | int test(_Bool Condition) { 18 | int X; 19 | if (Condition) 20 | X = G; 21 | else 22 | X = H; 23 | return X; 24 | } 25 | ``` 26 | 27 | 在本例中,我们有变量“X”,它的值取决于程序中执行的路径。因为在返回指令之前X有两个不同的可能值,所以插入一个PHI节点来合并这两个值。本例需要的LLVM IR如下所示: 28 | 29 | ```llvm 30 | @G = weak global i32 0 ; type of @G is i32* 31 | @H = weak global i32 0 ; type of @H is i32* 32 | 33 | define i32 @test(i1 %Condition) { 34 | entry: 35 | br i1 %Condition, label %cond_true, label %cond_false 36 | 37 | cond_true: 38 | %X.0 = load i32, i32* @G 39 | br label %cond_next 40 | 41 | cond_false: 42 | %X.1 = load i32, i32* @H 43 | br label %cond_next 44 | 45 | cond_next: 46 | %X.2 = phi i32 [ %X.1, %cond_false ], [ %X.0, %cond_true ] 47 | ret i32 %X.2 48 | } 49 | ``` 50 | 51 | 在本例中,来自G和H全局变量的加载在LLVM IR中是显式的,它们位于if语句(cond_true/cond_false)的THEN/ELSE分支中。为了合并传入的值,COND_NEXT block中的X.2 φ节点根据控制流来自何处选择要使用的正确值:如果控制流来自COND_FALSE Block,则X.2获取X.1的值。或者,如果控制流来自cond_true,它将获得X.0的值。本章的目的不是解释SSA表单的细节。有关详细信息,请参阅众多[线上参考资料](http://en.wikipedia.org/wiki/Static_single_assignment_form).]中的一个。 52 | 53 | 本文的问题是“对可变变量赋值降维时,谁放置φ节点?”。这里的问题是llvm*需要*它的IR必须是ssa形式的:它没有“非ssa”模式。但是,SSA的构建需要不平凡的算法和数据结构,所以每个前端都要重现这个逻辑是浪费并且不方便的。 54 | 55 | ## LLVM中的内存 56 | 57 | 这里的“诀窍”是,虽然LLVM确实要求所有寄存器值都采用SSA格式,但它并不要求(或允许)内存对象采用SSA格式。在上面的示例中,请注意来自G和H的载荷是对G和H的直接访问:它们没有重命名或版本化。这与其他一些编译器系统不同,其他编译器系统确实会尝试对内存对象进行版本化。在LLVM中,不是将内存的数据流分析编码到LLVM IR中,而是使用按需计算的[分析通道(Analysis Passes)](../../WritingAnLLVMPass.html)进行处理。 58 | 59 | 考虑到这一点,高级想法是我们希望为函数中的每个可变对象创建一个堆栈变量(它驻留在内存中,因为它在堆栈上)。要利用此技巧,我们需要讨论LLVM如何表示堆栈变量。 60 | 61 | 在LLVM中,所有内存访问都是使用加载/存储指令显式进行的,并且它被精心设计为不具有(或不需要)“address-of”运算符。请注意,即使变量定义为“I32”,\@G/\@H全局变量的类型实际上也是“I32\*”。这意味着,\@G在全局数据区域中为I32定义了*空间*,但它的*名字*实际上是指该空间的地址。堆栈变量的工作方式相同,不同之处在于它们不是使用全局变量定义声明的,而是使用[LLVM Alloca instruction](../../LangRef.html#alloca-instruction):]声明的 62 | 63 | ```llvm 64 | define i32 @example() { 65 | entry: 66 | %X = alloca i32 ; type of %X is i32*. 67 | ... 68 | %tmp = load i32, i32* %X ; load the stack value %X from the stack. 69 | %tmp2 = add i32 %tmp, 1 ; increment it 70 | store i32 %tmp2, i32* %X ; store it back 71 | ... 72 | ``` 73 | 74 | 此代码显示了如何在LLVM IR中声明和操作堆栈变量的示例。使用alloca指令分配的堆栈内存是完全通用的:您可以将堆栈槽的地址传递给函数,也可以将其存储在其他变量中,依此类推。在上面的示例中,我们可以重写示例以使用alloca技术来避免使用PHI节点: 75 | 76 | ```llvm 77 | @G = weak global i32 0 ; type of @G is i32* 78 | @H = weak global i32 0 ; type of @H is i32* 79 | 80 | define i32 @test(i1 %Condition) { 81 | entry: 82 | %X = alloca i32 ; type of %X is i32*. 83 | br i1 %Condition, label %cond_true, label %cond_false 84 | 85 | cond_true: 86 | %X.0 = load i32, i32* @G 87 | store i32 %X.0, i32* %X ; Update X 88 | br label %cond_next 89 | 90 | cond_false: 91 | %X.1 = load i32, i32* @H 92 | store i32 %X.1, i32* %X ; Update X 93 | br label %cond_next 94 | 95 | cond_next: 96 | %X.2 = load i32, i32* %X ; Read X 97 | ret i32 %X.2 98 | } 99 | ``` 100 | 101 | 这样,我们就发现了一种处理任意可变变量的方法,而根本不需要创建Phi节点: 102 | 103 | 1. 每个可变变量都由堆栈分配。 104 | 2. 每次读取变量都会成为堆栈中的加载load。 105 | 3. 变量的每次更新都会成为堆栈的存储store。 106 | 4. 获取变量的地址只需直接使用堆栈地址。 107 | 108 | 虽然这个解决方案解决了我们眼前的问题,但它引入了另一个问题:我们现在显然为非常简单和常见的操作引入了大量堆栈流量,这是一个主要的性能问题。对我们来说幸运的是,LLVM优化器有一个名为“mem2reg”的高度调优的优化通道来处理这种情况,它会将这样的分配提升到SSA寄存器中,并在适当的时候插入Phi节点。例如,如果通过该过程运行此示例,您将获得: 109 | 110 | ```bash 111 | $ llvm-as < example.ll | opt -mem2reg | llvm-dis 112 | @G = weak global i32 0 113 | @H = weak global i32 0 114 | 115 | define i32 @test(i1 %Condition) { 116 | entry: 117 | br i1 %Condition, label %cond_true, label %cond_false 118 | 119 | cond_true: 120 | %X.0 = load i32, i32* @G 121 | br label %cond_next 122 | 123 | cond_false: 124 | %X.1 = load i32, i32* @H 125 | br label %cond_next 126 | 127 | cond_next: 128 | %X.01 = phi i32 [ %X.1, %cond_false ], [ %X.0, %cond_true ] 129 | ret i32 %X.01 130 | } 131 | ``` 132 | 133 | mem2reg pass实现了用于构建SSA表单的标准“迭代优势边界(iterated dominance frontier)”算法,并进行了许多优化以加速(非常常见的)退化情况。mem2reg优化通道是处理可变变量的答案,我们强烈建议您依赖它。请注意,mem2reg仅在某些情况下适用于变量: 134 | 135 | 1. mem2reg是由alloca驱动的:它查找alloca,如果它能处理它们,它就会提升它们。它不适用于全局变量或堆分配。 136 | 2. mem2reg只在函数的entry Block中查找alloca指令。在entry Block中可以保证alloca只执行一次,这使得分析更简单。 137 | 3. mem2reg仅提升用途是直接加载和存储的alloca。如果将堆栈对象的地址传递给函数,或者如果涉及任何有趣的指针算法,则不会提升alloca。 138 | 4. mem2reg仅适用于[First class](../../LangRef.html#first-class-type)值的alloca(如指针、标量和向量),并且仅当allocation的数组大小为1(或.ll文件中缺少)时才有效。mem2reg不能将结构或数组提升到寄存器。请注意,“sroa”通道功能更强大,在许多情况下可以提升struct、“union”和array。 139 | 140 | 对于大多数命令式语言来说,所有这些属性都很容易满足,我们将在下面用Kaleidoscope进行说明。您可能会问的最后一个问题是:我是否应该在前端进行这种无意义的折腾?如果我直接进行SSA构造,避免使用mem2reg优化通道,不是更好吗?简而言之,我们强烈建议您使用此技术来构建SSA表单,除非有非常好的理由不这样做。使用此技术是: 141 | 142 | - 经过验证和良好测试:Clang将此技术用于局部可变变量。因此,LLVM最常见的客户端使用它来处理它们的大部分变量。您可以确保快速发现并及早修复错误。 143 | - 极快:mem2reg有许多特殊情况,这使得它在普通情况下和完全通用情况下都很快。例如,它具有只在单个Block中使用的变量的快速路径,只有一个赋值点的变量,避免插入不需要的φ节点的良好启发式方法,等等。 144 | - 生成调试信息所需:[LLVM中的调试信息](../../SourceLevelDebugging.html)依赖于公开变量的地址,以便可以附加调试信息。这种技术与这种风格的调试信息非常自然地吻合。 145 | 146 | 如果没有其他问题,这将使您的前端更容易启动和运行,并且实现起来非常简单。现在让我们用可变变量来扩展Kaleidoscope! 147 | 148 | ## Kaleidoscope中的可变变量 149 | 150 | 现在我们知道了我们想要解决的问题类型,让我们看看这在我们的Kaleidoscope语言的上下文中是什么样子。我们将添加两个功能: 151 | 152 | 1. 使用‘=’运算符修改变量的能力。 153 | 2. 定义新变量的能力。 154 | 155 | 尽管第一项实际上是关于这一点的,但我们只有用于传入参数和推导变量的变量,重新定义这些变量也就到此为止了:)。此外,定义新变量的能力是一件很有用的事情,无论您是否要对它们进行修改。下面是一个鼓舞人心的例子,它展示了我们如何使用这些: 156 | ``` 157 | # Define ':' for sequencing: as a low-precedence operator that ignores operands 158 | # and just returns the RHS. 159 | def binary : 1 (x y) y; 160 | 161 | # Recursive fib, we could do this before. 162 | def fib(x) 163 | if (x < 3) then 164 | 1 165 | else 166 | fib(x-1)+fib(x-2); 167 | 168 | # Iterative fib. 169 | def fibi(x) 170 | var a = 1, b = 1, c in 171 | (for i = 3, i < x in 172 | c = a + b : 173 | a = b : 174 | b = c) : 175 | b; 176 | 177 | # Call it. 178 | fibi(10); 179 | ``` 180 | 181 | 为了使变量发生改变,我们必须更改现有变量以使用“alloca技巧”。完成后,我们将添加新的运算符,然后扩展Kaleidoscope以支持新的变量定义。 182 | 183 | ## 调整现有变量以进行改变 184 | 185 | Kaleidoscope中的符号表在代码生成时由‘`NamedValues`’映射管理。此映射当前跟踪保存已命名变量的双精度值的LLVM“value\*”。为了支持修改,我们需要稍微更改一下,以便`NamedValues`保存需要修改变量的*内存位置*。请注意,此更改是一种重构:它更改了代码的结构,但(本身)不更改编译器的行为。所有这些更改都隔离在Kaleidoscope代码生成器中。 186 | 187 | 在Kaleidoscope开发的这一点上,它只支持两件事的变量:函数的传入参数和‘for’循环的推导变量。为了保持一致性,除了其他用户定义的变量外,我们还允许这些变量的改变。这意味着这些变量都需要内存位置。 188 | 189 | 要开始转换Kaleidoscope,我们将更改NamedValues映射,使其映射到AllocaInst\*而不是Value\*。完成此操作后,C++编译器将告诉我们需要更新代码的哪些部分: 190 | 191 | ```c++ 192 | static std::map NamedValues; 193 | ``` 194 | 195 | 另外,由于我们将需要创建这些allocas,因此我们将使用一个助手函数来确保在函数的entry Block中创建allocas: 196 | 197 | ```c++ 198 | /// CreateEntryBlockAlloca - Create an alloca instruction in the entry block of 199 | /// the function. This is used for mutable variables etc. 200 | static AllocaInst *CreateEntryBlockAlloca(Function *TheFunction, 201 | const std::string &VarName) { 202 | IRBuilder<> TmpB(&TheFunction->getEntryBlock(), 203 | TheFunction->getEntryBlock().begin()); 204 | return TmpB.CreateAlloca(Type::getDoubleTy(TheContext), 0, 205 | VarName.c_str()); 206 | } 207 | ``` 208 | 209 | 这段看起来很滑稽的代码创建了一个IRBuilder对象,该对象指向Blockentry 的第一条指令(.Begin())。然后,它创建一个具有预期名称的alloca并返回它。因为Kaleidoscope中的所有值都是双精度值,所以不需要传入类型即可使用。 210 | 211 | 有了这一点,我们要进行的第一个功能更改属于变量引用。在我们的新方案中,变量驻留在堆栈中,因此生成对它们的引用的代码实际上需要从堆栈插槽生成加载: 212 | 213 | ```c++ 214 | Value *VariableExprAST::codegen() { 215 | // Look this variable up in the function. 216 | Value *V = NamedValues[Name]; 217 | if (!V) 218 | return LogErrorV("Unknown variable name"); 219 | 220 | // Load the value. 221 | return Builder.CreateLoad(V, Name.c_str()); 222 | } 223 | ``` 224 | 225 | 如您所见,这非常简单。现在我们需要更新定义变量的内容来设置alloca。我们将从`ForExprAST::codegen()`开始(未删节的代码参见[完整代码清单](#Id1)): 226 | 227 | ```c++ 228 | Function *TheFunction = Builder.GetInsertBlock()->getParent(); 229 | 230 | // Create an alloca for the variable in the entry block. 231 | AllocaInst *Alloca = CreateEntryBlockAlloca(TheFunction, VarName); 232 | 233 | // Emit the start code first, without 'variable' in scope. 234 | Value *StartVal = Start->codegen(); 235 | if (!StartVal) 236 | return nullptr; 237 | 238 | // Store the value into the alloca. 239 | Builder.CreateStore(StartVal, Alloca); 240 | ... 241 | 242 | // Compute the end condition. 243 | Value *EndCond = End->codegen(); 244 | if (!EndCond) 245 | return nullptr; 246 | 247 | // Reload, increment, and restore the alloca. This handles the case where 248 | // the body of the loop mutates the variable. 249 | Value *CurVar = Builder.CreateLoad(Alloca); 250 | Value *NextVar = Builder.CreateFAdd(CurVar, StepVal, "nextvar"); 251 | Builder.CreateStore(NextVar, Alloca); 252 | ... 253 | ``` 254 | 255 | 此代码实际上与[在我们允许可变variables](zh-LangImpl05.html#code-generation-for-the-for-loop).之前]的代码相同。最大的区别在于,我们不再需要构造PHI节点,而是根据需要使用加载(load)/存储(store)来访问变量。 256 | 257 | 为了支持可变参数变量,我们还需要为它们进行分配。这方面的代码也非常简单: 258 | 259 | ```c++ 260 | Function *FunctionAST::codegen() { 261 | ... 262 | Builder.SetInsertPoint(BB); 263 | 264 | // Record the function arguments in the NamedValues map. 265 | NamedValues.clear(); 266 | for (auto &Arg : TheFunction->args()) { 267 | // Create an alloca for this variable. 268 | AllocaInst *Alloca = CreateEntryBlockAlloca(TheFunction, Arg.getName()); 269 | 270 | // Store the initial value into the alloca. 271 | Builder.CreateStore(&Arg, Alloca); 272 | 273 | // Add arguments to variable symbol table. 274 | NamedValues[Arg.getName()] = Alloca; 275 | } 276 | 277 | if (Value *RetVal = Body->codegen()) { 278 | ... 279 | ``` 280 | 281 | 对于每个参数,我们创建一个Alloca,将函数的输入值存储到Alloca中,并将Alloca注册为参数的内存位置。此方法由`FunctionAST::codegen()`在为函数设置entry Block后立即调用。 282 | 283 | 最后缺少的部分是添加mem2reg pass,它允许我们再次获得良好的编解码器: 284 | 285 | ```c++ 286 | // Promote allocas to registers. 287 | TheFPM->add(createPromoteMemoryToRegisterPass()); 288 | // Do simple "peephole" optimizations and bit-twiddling optzns. 289 | TheFPM->add(createInstructionCombiningPass()); 290 | // Reassociate expressions. 291 | TheFPM->add(createReassociatePass()); 292 | ... 293 | ``` 294 | 295 | 看看mem2reg优化运行前后的代码是什么样子是很有趣的。例如,这是我们的递归fib函数的前后代码。优化前: 296 | 297 | ```llvm 298 | define double @fib(double %x) { 299 | entry: 300 | %x1 = alloca double 301 | store double %x, double* %x1 302 | %x2 = load double, double* %x1 303 | %cmptmp = fcmp ult double %x2, 3.000000e+00 304 | %booltmp = uitofp i1 %cmptmp to double 305 | %ifcond = fcmp one double %booltmp, 0.000000e+00 306 | br i1 %ifcond, label %then, label %else 307 | 308 | then: ; preds = %entry 309 | br label %ifcont 310 | 311 | else: ; preds = %entry 312 | %x3 = load double, double* %x1 313 | %subtmp = fsub double %x3, 1.000000e+00 314 | %calltmp = call double @fib(double %subtmp) 315 | %x4 = load double, double* %x1 316 | %subtmp5 = fsub double %x4, 2.000000e+00 317 | %calltmp6 = call double @fib(double %subtmp5) 318 | %addtmp = fadd double %calltmp, %calltmp6 319 | br label %ifcont 320 | 321 | ifcont: ; preds = %else, %then 322 | %iftmp = phi double [ 1.000000e+00, %then ], [ %addtmp, %else ] 323 | ret double %iftmp 324 | } 325 | ``` 326 | 327 | 这里只有一个变量(x,输入参数),但是您仍然可以看到我们正在使用的极其简单的代码生成策略。在entry Block中,创建一个alloca,并将初始输入值存储在其中。每个对变量的引用都会从堆栈重新加载一次。另外,请注意,我们没有修改if/Then/Else表达式,所以它仍然插入一个PHI节点。虽然我们可以为它创建一个alloca,但实际上为它创建一个PHI节点更容易,所以我们仍然只创建PHI。 328 | 329 | 以下是mem2reg传递运行后的代码: 330 | 331 | ```llvm 332 | define double @fib(double %x) { 333 | entry: 334 | %cmptmp = fcmp ult double %x, 3.000000e+00 335 | %booltmp = uitofp i1 %cmptmp to double 336 | %ifcond = fcmp one double %booltmp, 0.000000e+00 337 | br i1 %ifcond, label %then, label %else 338 | 339 | then: 340 | br label %ifcont 341 | 342 | else: 343 | %subtmp = fsub double %x, 1.000000e+00 344 | %calltmp = call double @fib(double %subtmp) 345 | %subtmp5 = fsub double %x, 2.000000e+00 346 | %calltmp6 = call double @fib(double %subtmp5) 347 | %addtmp = fadd double %calltmp, %calltmp6 348 | br label %ifcont 349 | 350 | ifcont: ; preds = %else, %then 351 | %iftmp = phi double [ 1.000000e+00, %then ], [ %addtmp, %else ] 352 | ret double %iftmp 353 | } 354 | ``` 355 | 356 | 对于mem2reg来说,这是一个微不足道的例子,因为没有重新定义变量。展示这一点的目的是为了平息你对插入这种明显的低效行为的紧张情绪:)。 357 | 358 | 优化器的剩余部分运行后,我们得到: 359 | 360 | ```llvm 361 | define double @fib(double %x) { 362 | entry: 363 | %cmptmp = fcmp ult double %x, 3.000000e+00 364 | %booltmp = uitofp i1 %cmptmp to double 365 | %ifcond = fcmp ueq double %booltmp, 0.000000e+00 366 | br i1 %ifcond, label %else, label %ifcont 367 | 368 | else: 369 | %subtmp = fsub double %x, 1.000000e+00 370 | %calltmp = call double @fib(double %subtmp) 371 | %subtmp5 = fsub double %x, 2.000000e+00 372 | %calltmp6 = call double @fib(double %subtmp5) 373 | %addtmp = fadd double %calltmp, %calltmp6 374 | ret double %addtmp 375 | 376 | ifcont: 377 | ret double 1.000000e+00 378 | } 379 | ``` 380 | 381 | 在这里我们可以看到,simplifycfg pass决定将返回指令克隆到‘Else’Block的末尾。这允许它消除一些分支和PHI节点。 382 | 383 | 现在所有符号表引用都更新为使用堆栈变量,我们将添加赋值运算符。 384 | 385 | ## 新建赋值运算符 386 | 387 | 使用我们当前的框架,添加一个新的赋值操作符非常简单。我们将像解析任何其他二元运算符一样解析它,但在内部处理它(而不是允许用户定义它)。第一步是设置优先级: 388 | 389 | ```c++ 390 | int main() { 391 | // Install standard binary operators. 392 | // 1 is lowest precedence. 393 | BinopPrecedence['='] = 2; 394 | BinopPrecedence['<'] = 10; 395 | BinopPrecedence['+'] = 20; 396 | BinopPrecedence['-'] = 20; 397 | ``` 398 | 399 | 既然解析器知道二元运算符的优先级,它就负责所有的解析和AST生成。我们只需要为赋值操作符实现codegen。这看起来像下文这样: 400 | 401 | ```c++ 402 | Value *BinaryExprAST::codegen() { 403 | // Special case '=' because we don't want to emit the LHS as an expression. 404 | if (Op == '=') { 405 | // Assignment requires the LHS to be an identifier. 406 | VariableExprAST *LHSE = dynamic_cast(LHS.get()); 407 | if (!LHSE) 408 | return LogErrorV("destination of '=' must be a variable"); 409 | ``` 410 | 411 | 与其他的二元运算符不同,我们的赋值运算符没有遵循“发出lhs,发出rh,做计算”的模型,所以在处理其他二元运算符之前,会将其作为特例来处理。另一个奇怪的事情是,它要求lhs是一个变量。有“(x+1)=expr”是无效的-只允许“x=expr”这样的东西。 412 | 413 | ```c++ 414 | // Codegen the RHS. 415 | Value *Val = RHS->codegen(); 416 | if (!Val) 417 | return nullptr; 418 | 419 | // Look up the name. 420 | Value *Variable = NamedValues[LHSE->getName()]; 421 | if (!Variable) 422 | return LogErrorV("Unknown variable name"); 423 | 424 | Builder.CreateStore(Val, Variable); 425 | return Val; 426 | } 427 | ... 428 | ``` 429 | 430 | 一旦我们有了变量,赋值的代码生成就很简单了:我们发出赋值的RHS,创建一个存储,并返回计算值。返回值允许像“X=(Y=Z)”这样的链式赋值。 431 | 432 | 现在我们有了赋值操作符,我们可以改变循环变量和参数。例如,我们现在可以运行如下代码: 433 | ``` 434 | # Function to print a double. 435 | extern printd(x); 436 | 437 | # Define ':' for sequencing: as a low-precedence operator that ignores operands 438 | # and just returns the RHS. 439 | def binary : 1 (x y) y; 440 | 441 | def test(x) 442 | printd(x) : 443 | x = 4 : 444 | printd(x); 445 | 446 | test(123); 447 | ``` 448 | 449 | 运行时,此示例打印“123”,然后打印“4”,表明我们确实改变了值!好的,我们现在已经正式实现了我们的目标:要想让它正常工作,一般情况下需要SSA构建。然而,为了真正有用,我们希望能够定义我们自己的局部变量,接下来让我们添加这个! 450 | 451 | ## 用户定义的局部变量 452 | 453 | 添加var/in就像我们对Kaleidoscope所做的任何其他扩展一样:我们扩展了词法分析器、解析器、AST和代码生成器。添加新的‘var/in’结构的第一步是扩展词法分析器。与前面一样,这非常简单,代码如下所示: 454 | 455 | ```c++ 456 | enum Token { 457 | ... 458 | // var definition 459 | tok_var = -13 460 | ... 461 | } 462 | ... 463 | static int gettok() { 464 | ... 465 | if (IdentifierStr == "in") 466 | return tok_in; 467 | if (IdentifierStr == "binary") 468 | return tok_binary; 469 | if (IdentifierStr == "unary") 470 | return tok_unary; 471 | if (IdentifierStr == "var") 472 | return tok_var; 473 | return tok_identifier; 474 | ... 475 | ``` 476 | 477 | 下一步是定义我们将构造的AST节点。对于var/in,如下所示: 478 | 479 | ```c++ 480 | /// VarExprAST - Expression class for var/in 481 | class VarExprAST : public ExprAST { 482 | std::vector>> VarNames; 483 | std::unique_ptr Body; 484 | 485 | public: 486 | VarExprAST(std::vector>> VarNames, 487 | std::unique_ptr Body) 488 | : VarNames(std::move(VarNames)), Body(std::move(Body)) {} 489 | 490 | Value *codegen() override; 491 | }; 492 | ``` 493 | 494 | var/in允许一次定义所有名称列表,并且每个名称可以有一个可选的初始值。这样,我们在VarNames矢量中捕获此信息。另外,var/in有一个主体(Body),这个主体允许访问由var/in定义的变量。 495 | 496 | 有了这些,我们就可以定义解析器部分了。我们要做的第一件事是将其添加为主表达式: 497 | 498 | ```c++ 499 | /// primary 500 | /// ::= identifierexpr 501 | /// ::= numberexpr 502 | /// ::= parenexpr 503 | /// ::= ifexpr 504 | /// ::= forexpr 505 | /// ::= varexpr 506 | static std::unique_ptr ParsePrimary() { 507 | switch (CurTok) { 508 | default: 509 | return LogError("unknown token when expecting an expression"); 510 | case tok_identifier: 511 | return ParseIdentifierExpr(); 512 | case tok_number: 513 | return ParseNumberExpr(); 514 | case '(': 515 | return ParseParenExpr(); 516 | case tok_if: 517 | return ParseIfExpr(); 518 | case tok_for: 519 | return ParseForExpr(); 520 | case tok_var: 521 | return ParseVarExpr(); 522 | } 523 | } 524 | ``` 525 | 526 | 接下来,我们定义ParseVarExpr: 527 | 528 | ```c++ 529 | /// varexpr ::= 'var' identifier ('=' expression)? 530 | // (',' identifier ('=' expression)?)* 'in' expression 531 | static std::unique_ptr ParseVarExpr() { 532 | getNextToken(); // eat the var. 533 | 534 | std::vector>> VarNames; 535 | 536 | // At least one variable name is required. 537 | if (CurTok != tok_identifier) 538 | return LogError("expected identifier after var"); 539 | ``` 540 | 541 | 这段代码的第一部分将标识符/表达式对的列表解析为本地的“VarNames`”向量。 542 | 543 | ```c++ 544 | while (1) { 545 | std::string Name = IdentifierStr; 546 | getNextToken(); // eat identifier. 547 | 548 | // Read the optional initializer. 549 | std::unique_ptr Init; 550 | if (CurTok == '=') { 551 | getNextToken(); // eat the '='. 552 | 553 | Init = ParseExpression(); 554 | if (!Init) return nullptr; 555 | } 556 | 557 | VarNames.push_back(std::make_pair(Name, std::move(Init))); 558 | 559 | // End of var list, exit loop. 560 | if (CurTok != ',') break; 561 | getNextToken(); // eat the ','. 562 | 563 | if (CurTok != tok_identifier) 564 | return LogError("expected identifier list after var"); 565 | } 566 | ``` 567 | 568 | 一旦解析完所有变量,我们就解析正文并创建AST节点: 569 | 570 | ```c++ 571 | // At this point, we have to have 'in'. 572 | if (CurTok != tok_in) 573 | return LogError("expected 'in' keyword after 'var'"); 574 | getNextToken(); // eat 'in'. 575 | 576 | auto Body = ParseExpression(); 577 | if (!Body) 578 | return nullptr; 579 | 580 | return std::make_unique(std::move(VarNames), 581 | std::move(Body)); 582 | } 583 | ``` 584 | 585 | 现在我们可以解析和表示代码了,我们需要支持它的LLVM IR发射。此代码以以下代码开头: 586 | 587 | ```c++ 588 | Value *VarExprAST::codegen() { 589 | std::vector OldBindings; 590 | 591 | Function *TheFunction = Builder.GetInsertBlock()->getParent(); 592 | 593 | // Register all variables and emit their initializer. 594 | for (unsigned i = 0, e = VarNames.size(); i != e; ++i) { 595 | const std::string &VarName = VarNames[i].first; 596 | ExprAST *Init = VarNames[i].second.get(); 597 | ``` 598 | 599 | 基本上,它循环所有变量,一次安装一个变量。对于我们放到符号表中的每个变量,我们都会记住在OldBindings中替换的前一个值。 600 | 601 | ```c++ 602 | // Emit the initializer before adding the variable to scope, this prevents 603 | // the initializer from referencing the variable itself, and permits stuff 604 | // like this: 605 | // var a = 1 in 606 | // var a = a in ... # refers to outer 'a'. 607 | Value *InitVal; 608 | if (Init) { 609 | InitVal = Init->codegen(); 610 | if (!InitVal) 611 | return nullptr; 612 | } else { // If not specified, use 0.0. 613 | InitVal = ConstantFP::get(TheContext, APFloat(0.0)); 614 | } 615 | 616 | AllocaInst *Alloca = CreateEntryBlockAlloca(TheFunction, VarName); 617 | Builder.CreateStore(InitVal, Alloca); 618 | 619 | // Remember the old variable binding so that we can restore the binding when 620 | // we unrecurse. 621 | OldBindings.push_back(NamedValues[VarName]); 622 | 623 | // Remember this binding. 624 | NamedValues[VarName] = Alloca; 625 | } 626 | ``` 627 | 628 | 这里的注释比代码多。基本思想是发出初始值设定项,创建alloca,然后更新符号表以指向它。一旦所有变量都安装到符号表中,我们将计算var/in表达式的主体(body): 629 | 630 | ```c++ 631 | // Codegen the body, now that all vars are in scope. 632 | Value *BodyVal = Body->codegen(); 633 | if (!BodyVal) 634 | return nullptr; 635 | ``` 636 | 637 | 最后,在返回之前,我们恢复前面的变量绑定: 638 | 639 | ```c++ 640 | // Pop all our variables from scope. 641 | for (unsigned i = 0, e = VarNames.size(); i != e; ++i) 642 | NamedValues[VarNames[i].first] = OldBindings[i]; 643 | 644 | // Return the body computation. 645 | return BodyVal; 646 | } 647 | ``` 648 | 649 | 所有这一切的最终结果是我们得到了适当范围的变量定义,并且我们甚至(微不足道地)允许对它们进行修改:)。 650 | 651 | 有了这个,我们就完成了我们开始要做的事情。我们从开头给出的漂亮的迭代fib示例编译得并运行得很好。mem2reg pass优化了SSA寄存器中的所有堆栈变量,在需要的地方插入PHI节点,并且我们的前端仍然很简单:在任何地方都看不到“迭代优势边界(iterated dominance frontier)”计算。 652 | 653 | 654 | [下一步:编译为对象代码](zh-LangImpl08.html) 655 | -------------------------------------------------------------------------------- /src/ch5_cfg.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by m0dulo on 2022/12/04. 3 | // 4 | 5 | #include "./include/KaleidoscopeJIT.h" 6 | #include "llvm/ADT/APFloat.h" 7 | #include "llvm/ADT/STLExtras.h" 8 | #include "llvm/IR/BasicBlock.h" 9 | #include "llvm/IR/Constants.h" 10 | #include "llvm/IR/DerivedTypes.h" 11 | #include "llvm/IR/Function.h" 12 | #include "llvm/IR/IRBuilder.h" 13 | #include "llvm/IR/Instructions.h" 14 | #include "llvm/IR/LLVMContext.h" 15 | #include "llvm/IR/LegacyPassManager.h" 16 | #include "llvm/IR/Module.h" 17 | #include "llvm/IR/Type.h" 18 | #include "llvm/IR/Verifier.h" 19 | #include "llvm/Support/TargetSelect.h" 20 | #include "llvm/Target/TargetMachine.h" 21 | #include "llvm/Transforms/InstCombine/InstCombine.h" 22 | #include "llvm/Transforms/Scalar.h" 23 | #include "llvm/Transforms/Scalar/GVN.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | using namespace llvm; 36 | using namespace llvm::orc; 37 | 38 | enum Token { 39 | tok_eof = -1, 40 | tok_def = -2, 41 | tok_extern = -3, 42 | tok_identifier = -4, 43 | tok_number = -5, 44 | 45 | tok_if = -6, 46 | tok_then = -7, 47 | tok_else = -8, 48 | tok_for = -9, 49 | tok_in = -10 50 | }; 51 | 52 | static std::string IdentifierStr; 53 | static double NumVal; 54 | 55 | static int gettok() { 56 | static int LastChar = ' '; 57 | 58 | while (isspace(LastChar)) 59 | LastChar = getchar(); 60 | 61 | if (isalpha(LastChar)) { 62 | IdentifierStr = LastChar; 63 | while (isalnum(LastChar = getchar())) 64 | IdentifierStr += LastChar; 65 | 66 | if (IdentifierStr == "def") 67 | return tok_def; 68 | if (IdentifierStr == "extern") 69 | return tok_extern; 70 | if (IdentifierStr == "if") 71 | return tok_if; 72 | if (IdentifierStr == "then") 73 | return tok_then; 74 | if (IdentifierStr == "else") 75 | return tok_else; 76 | if (IdentifierStr == "for") 77 | return tok_for; 78 | if (IdentifierStr == "in") 79 | return tok_in; 80 | return tok_identifier; 81 | } 82 | 83 | if (isdigit(LastChar) || LastChar == '.') { 84 | std::string NumStr; 85 | do { 86 | NumStr += LastChar; 87 | LastChar = getchar(); 88 | } while (isdigit(LastChar) || LastChar == '.'); 89 | 90 | NumVal = strtod(NumStr.c_str(), nullptr); 91 | return tok_number; 92 | } 93 | 94 | if (LastChar == '#') { 95 | do 96 | LastChar = getchar(); 97 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 98 | 99 | if (LastChar != EOF) 100 | return gettok(); 101 | } 102 | 103 | if (LastChar == EOF) 104 | return tok_eof; 105 | 106 | int ThisChar = LastChar; 107 | LastChar = getchar(); 108 | return ThisChar; 109 | } 110 | 111 | namespace { 112 | 113 | class ExprAST { 114 | public: 115 | virtual ~ExprAST() = default; 116 | virtual Value *codegen() = 0; 117 | }; 118 | 119 | class NumberExprAST : public ExprAST { 120 | double Val; 121 | 122 | public: 123 | NumberExprAST(double Val) : Val(Val) {} 124 | Value *codegen() override; 125 | }; 126 | 127 | class VariableExprAST : public ExprAST { 128 | std::string Name; 129 | 130 | public: 131 | VariableExprAST(const std::string &Name) : Name(Name) {} 132 | Value *codegen() override; 133 | }; 134 | 135 | class BinaryExprAST : public ExprAST { 136 | char Op; 137 | std::unique_ptr LHS, RHS; 138 | 139 | public: 140 | BinaryExprAST(char Op, std::unique_ptr LHS, 141 | std::unique_ptr RHS) 142 | : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 143 | Value *codegen() override; 144 | }; 145 | 146 | class CallExprAST : public ExprAST { 147 | std::string Callee; 148 | std::vector> Args; 149 | 150 | public: 151 | CallExprAST(const std::string &Callee, 152 | std::vector> Args) 153 | : Callee(Callee), Args(std::move(Args)) {} 154 | Value *codegen() override; 155 | }; 156 | 157 | class IfExprAST : public ExprAST { 158 | std::unique_ptr Cond, Then, Else; 159 | 160 | public: 161 | IfExprAST(std::unique_ptr Cond, std::unique_ptr Then, 162 | std::unique_ptr Else) 163 | : Cond(std::move(Cond)), Then(std::move(Then)), Else(std::move(Else)) {} 164 | 165 | Value *codegen() override; 166 | }; 167 | 168 | 169 | class ForExprAST : public ExprAST { 170 | std::string VarName; 171 | std::unique_ptr Start, End, Step, Body; 172 | 173 | public: 174 | ForExprAST(const std::string &VarName, std::unique_ptr Start, 175 | std::unique_ptr End, std::unique_ptr Step, 176 | std::unique_ptr Body) 177 | : VarName(VarName), Start(std::move(Start)), End(std::move(End)), 178 | Step(std::move(Step)), Body(std::move(Body)) {} 179 | 180 | Value *codegen() override; 181 | }; 182 | 183 | class PrototypeAST { 184 | std::string Name; 185 | std::vector Args; 186 | 187 | public: 188 | PrototypeAST(const std::string &Name, std::vector Args) 189 | : Name(Name), Args(std::move(Args)) {} 190 | 191 | const std::string &getName() const { return Name; } 192 | Function *codegen(); 193 | }; 194 | 195 | class FunctionAST { 196 | std::unique_ptr Proto; 197 | std::unique_ptr Body; 198 | 199 | public: 200 | FunctionAST(std::unique_ptr Proto, 201 | std::unique_ptr Body) 202 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 203 | Function *codegen(); 204 | }; 205 | 206 | } 207 | 208 | static int CurTok; 209 | static int getNextToken() { return CurTok = gettok(); } 210 | 211 | static std::map BinopPrecedence; 212 | 213 | static int GetTokPrecedence() { 214 | if (!isascii(CurTok)) 215 | return -1; 216 | 217 | int TokPrec = BinopPrecedence[CurTok]; 218 | if (TokPrec <= 0) 219 | return -1; 220 | return TokPrec; 221 | } 222 | 223 | std::unique_ptr LogError(const char *Str) { 224 | fprintf(stderr, "Error: %s\n", Str); 225 | return nullptr; 226 | } 227 | 228 | std::unique_ptr LogErrorP(const char *Str) { 229 | LogError(Str); 230 | return nullptr; 231 | } 232 | 233 | static std::unique_ptr ParseExpression(); 234 | 235 | static std::unique_ptr ParseNumberExpr() { 236 | auto Result = std::make_unique(NumVal); 237 | getNextToken(); 238 | return std::move(Result); 239 | } 240 | 241 | static std::unique_ptr ParseParenExpr() { 242 | getNextToken(); // eat ( 243 | auto V = ParseExpression(); 244 | if (!V) 245 | return nullptr; 246 | 247 | if (CurTok != ')') 248 | return LogError("expected ')'"); 249 | getNextToken(); // eat ')' 250 | return V; 251 | } 252 | 253 | static std::unique_ptr ParseIdentifierExpr() { 254 | std::string IdName = IdentifierStr; 255 | 256 | getNextToken(); 257 | 258 | if (CurTok != '(') 259 | return std::make_unique(IdName); 260 | 261 | getNextToken(); 262 | std::vector> Args; 263 | if (CurTok != ')') { 264 | while (true) { 265 | if (auto Arg = ParseExpression()) 266 | Args.push_back(std::move(Arg)); 267 | else 268 | return nullptr; 269 | 270 | if (CurTok == ')') 271 | break; 272 | 273 | if (CurTok != ',') 274 | return LogError("Expected ')' or ',' in argument list"); 275 | getNextToken(); 276 | } 277 | } 278 | 279 | getNextToken(); // eat ')' 280 | 281 | return std::make_unique(IdName, std::move(Args)); 282 | } 283 | 284 | static std::unique_ptr ParseIfExpr() { 285 | getNextToken(); 286 | 287 | auto Cond = ParseExpression(); 288 | if (!Cond) 289 | return nullptr; 290 | 291 | if (CurTok != tok_then) 292 | return LogError("expected then"); 293 | getNextToken(); 294 | 295 | auto Then = ParseExpression(); 296 | if (!Then) 297 | return nullptr; 298 | 299 | if (CurTok != tok_else) 300 | return LogError("expected else"); 301 | getNextToken(); 302 | 303 | auto Else = ParseExpression(); 304 | if (!Else) 305 | return nullptr; 306 | 307 | return std::make_unique(std::move(Cond), std::move(Then), 308 | std::move(Else)); 309 | } 310 | 311 | static std::unique_ptr ParseForExpr() { 312 | getNextToken(); 313 | 314 | if (CurTok != tok_identifier) 315 | return LogError("expected identifier after for"); 316 | 317 | std::string IdName = IdentifierStr; 318 | getNextToken(); 319 | 320 | if (CurTok != '=') 321 | return LogError("expected '=' after for"); 322 | getNextToken(); 323 | 324 | auto Start = ParseExpression(); 325 | if (!Start) 326 | return nullptr; 327 | if (CurTok != ',') 328 | return LogError("expected ',' after for start value"); 329 | getNextToken(); 330 | 331 | auto End = ParseExpression(); 332 | if (!End) 333 | return nullptr; 334 | 335 | std::unique_ptr Step; 336 | if (CurTok == ',') { 337 | getNextToken(); 338 | Step = ParseExpression(); 339 | if (!Step) 340 | return nullptr; 341 | } 342 | 343 | if (CurTok != tok_in) 344 | return LogError("expected 'in' after for"); 345 | getNextToken(); 346 | 347 | auto Body = ParseExpression(); 348 | if (!Body) 349 | return nullptr; 350 | 351 | return std::make_unique(IdName, std::move(Start), std::move(End), 352 | std::move(Step), std::move(Body)); 353 | } 354 | 355 | static std::unique_ptr ParsePrimary() { 356 | switch (CurTok) { 357 | default: 358 | return LogError("unknown token when expecting an expression"); 359 | case tok_identifier: 360 | return ParseIdentifierExpr(); 361 | case tok_number: 362 | return ParseNumberExpr(); 363 | case '(': 364 | return ParseParenExpr(); 365 | case tok_if: 366 | return ParseIfExpr(); 367 | case tok_for: 368 | return ParseForExpr(); 369 | } 370 | } 371 | 372 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 373 | std::unique_ptr LHS) { 374 | while (true) { 375 | int TokPrec = GetTokPrecedence(); 376 | 377 | if (TokPrec < ExprPrec) 378 | return LHS; 379 | 380 | int BinOp = CurTok; 381 | getNextToken(); 382 | 383 | auto RHS = ParsePrimary(); 384 | if (!RHS) 385 | return nullptr; 386 | 387 | int NextPrec = GetTokPrecedence(); 388 | if (TokPrec < NextPrec) { 389 | RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS)); 390 | if (!RHS) 391 | return nullptr; 392 | } 393 | 394 | LHS = 395 | std::make_unique(BinOp, std::move(LHS), std::move(RHS)); 396 | } 397 | } 398 | 399 | static std::unique_ptr ParseExpression() { 400 | auto LHS = ParsePrimary(); 401 | if (!LHS) 402 | return nullptr; 403 | 404 | return ParseBinOpRHS(0, std::move(LHS)); 405 | } 406 | 407 | static std::unique_ptr ParsePrototype() { 408 | if (CurTok != tok_identifier) 409 | return LogErrorP("Expected function name in prototype"); 410 | 411 | std::string FnName = IdentifierStr; 412 | getNextToken(); 413 | 414 | if (CurTok != '(') 415 | return LogErrorP("Expected '(' in prototype"); 416 | 417 | std::vector ArgNames; 418 | while (getNextToken() == tok_identifier) 419 | ArgNames.push_back(IdentifierStr); 420 | if (CurTok != ')') 421 | return LogErrorP("Expected ')' in prototype"); 422 | 423 | getNextToken(); 424 | 425 | return std::make_unique(FnName, std::move(ArgNames)); 426 | } 427 | 428 | static std::unique_ptr ParseDefinition() { 429 | getNextToken(); 430 | auto Proto = ParsePrototype(); 431 | if (!Proto) 432 | return nullptr; 433 | 434 | if (auto E = ParseExpression()) 435 | return std::make_unique(std::move(Proto), std::move(E)); 436 | return nullptr; 437 | } 438 | 439 | static std::unique_ptr ParseTopLevelExpr() { 440 | if (auto E = ParseExpression()) { 441 | auto Proto = std::make_unique("__anon_expr", 442 | std::vector()); 443 | return std::make_unique(std::move(Proto), std::move(E)); 444 | } 445 | return nullptr; 446 | } 447 | 448 | static std::unique_ptr ParseExtern() { 449 | getNextToken(); 450 | return ParsePrototype(); 451 | } 452 | 453 | 454 | static std::unique_ptr TheContext; 455 | static std::unique_ptr TheModule; 456 | static std::unique_ptr> Builder; 457 | static std::map NamedValues; 458 | 459 | static std::unique_ptr TheFPM; 460 | static std::unique_ptr TheJIT; 461 | static std::map> FunctionProtos; 462 | static ExitOnError ExitOnErr; 463 | 464 | Value *LogErrorV(const char *Str) { 465 | LogError(Str); 466 | return nullptr; 467 | } 468 | 469 | Function *getFunction(std::string Name) { 470 | if (auto *F = TheModule->getFunction(Name)) 471 | return F; 472 | 473 | auto FI = FunctionProtos.find(Name); 474 | if (FI != FunctionProtos.end()) 475 | return FI->second->codegen(); 476 | 477 | return nullptr; 478 | } 479 | 480 | Value *NumberExprAST::codegen() { 481 | return ConstantFP::get(*TheContext, APFloat(Val)); 482 | } 483 | 484 | Value *VariableExprAST::codegen() { 485 | // Look this variable up in the function. 486 | Value *V = NamedValues[Name]; 487 | if (!V) 488 | return LogErrorV("Unknown variable name"); 489 | return V; 490 | } 491 | 492 | Value *BinaryExprAST::codegen() { 493 | Value *L = LHS->codegen(); 494 | Value *R = RHS->codegen(); 495 | if (!L || !R) 496 | return nullptr; 497 | 498 | switch (Op) { 499 | case '+': 500 | return Builder->CreateFAdd(L, R, "addtmp"); 501 | case '-': 502 | return Builder->CreateFSub(L, R, "subtmp"); 503 | case '*': 504 | return Builder->CreateFMul(L, R, "multmp"); 505 | case '<': 506 | L = Builder->CreateFCmpULT(L, R, "cmptmp"); 507 | 508 | return Builder->CreateUIToFP(L, Type::getDoubleTy(*TheContext), "booltmp"); 509 | default: 510 | return LogErrorV("invalid binary operator"); 511 | } 512 | } 513 | 514 | Value *CallExprAST::codegen() { 515 | 516 | Function *CalleeF = TheModule->getFunction(Callee); 517 | if (!CalleeF) 518 | return LogErrorV("Unknown function referenced"); 519 | if (CalleeF->arg_size() != Args.size()) 520 | return LogErrorV("Incorrect # arguments passed"); 521 | 522 | std::vector ArgsV; 523 | for (unsigned i = 0, e = Args.size(); i != e; ++i) { 524 | ArgsV.push_back(Args[i]->codegen()); 525 | if (!ArgsV.back()) 526 | return nullptr; 527 | } 528 | 529 | return Builder->CreateCall(CalleeF, ArgsV, "calltmp"); 530 | } 531 | 532 | Value *IfExprAST::codegen() { 533 | Value *CondV = Cond->codegen(); 534 | if (!CondV) 535 | return nullptr; 536 | 537 | CondV = Builder->CreateFCmpONE( 538 | CondV, ConstantFP::get(*TheContext, APFloat(0.0)), "ifcond"); 539 | 540 | Function *TheFunction = Builder->GetInsertBlock()->getParent(); 541 | 542 | BasicBlock *ThenBB = BasicBlock::Create(*TheContext, "then", TheFunction); 543 | BasicBlock *ElseBB = BasicBlock::Create(*TheContext, "else"); 544 | BasicBlock *MergeBB = BasicBlock::Create(*TheContext, "ifcont"); 545 | 546 | Builder->CreateCondBr(CondV, ThenBB, ElseBB); 547 | 548 | Builder->SetInsertPoint(ThenBB); 549 | 550 | Value *ThenV = Then->codegen(); 551 | if (!ThenV) 552 | return nullptr; 553 | 554 | Builder->CreateBr(MergeBB); 555 | ThenBB = Builder->GetInsertBlock(); 556 | 557 | TheFunction->getBasicBlockList().push_back(ElseBB); 558 | Builder->SetInsertPoint(ElseBB); 559 | 560 | Value *ElseV = Else->codegen(); 561 | if (!ElseV) 562 | return nullptr; 563 | 564 | Builder->CreateBr(MergeBB); 565 | ElseBB = Builder->GetInsertBlock(); 566 | 567 | TheFunction->getBasicBlockList().push_back(MergeBB); 568 | Builder->SetInsertPoint(MergeBB); 569 | PHINode *PN = Builder->CreatePHI(Type::getDoubleTy(*TheContext), 2, "iftmp"); 570 | 571 | PN->addIncoming(ThenV, ThenBB); 572 | PN->addIncoming(ElseV, ElseBB); 573 | return PN; 574 | } 575 | 576 | Value *ForExprAST::codegen() { 577 | Value *StartVal = Start->codegen(); 578 | if (!StartVal) 579 | return nullptr; 580 | 581 | Function *TheFunction = Builder->GetInsertBlock()->getParent(); 582 | BasicBlock *PreheaderBB = Builder->GetInsertBlock(); 583 | BasicBlock *LoopBB = BasicBlock::Create(*TheContext, "loop", TheFunction); 584 | 585 | Builder->CreateBr(LoopBB); 586 | Builder->SetInsertPoint(LoopBB); 587 | 588 | PHINode *Variable = 589 | Builder->CreatePHI(Type::getDoubleTy(*TheContext), 2, VarName); 590 | Variable->addIncoming(StartVal, PreheaderBB); 591 | 592 | Value *OldVal = NamedValues[VarName]; 593 | NamedValues[VarName] = Variable; 594 | 595 | if (!Body->codegen()) 596 | return nullptr; 597 | 598 | Value *StepVal = nullptr; 599 | if (Step) { 600 | StepVal = Step->codegen(); 601 | if (!StepVal) 602 | return nullptr; 603 | } else { 604 | StepVal = ConstantFP::get(*TheContext, APFloat(1.0)); 605 | } 606 | 607 | Value *NextVar = Builder->CreateFAdd(Variable, StepVal, "nextvar"); 608 | 609 | Value *EndCond = End->codegen(); 610 | if (!EndCond) 611 | return nullptr; 612 | 613 | EndCond = Builder->CreateFCmpONE( 614 | EndCond, ConstantFP::get(*TheContext, APFloat(1.0)), "loopcond"); 615 | 616 | BasicBlock *LoopEndBB = Builder->GetInsertBlock(); 617 | BasicBlock *AfterBB = 618 | BasicBlock::Create(*TheContext, "afterloop", TheFunction); 619 | 620 | Builder->CreateCondBr(EndCond, LoopBB, AfterBB); 621 | Builder->SetInsertPoint(AfterBB); 622 | 623 | Variable->addIncoming(NextVar, LoopEndBB); 624 | 625 | if (OldVal) 626 | NamedValues[VarName] = OldVal; 627 | else 628 | NamedValues.erase(VarName); 629 | 630 | return Constant::getNullValue(Type::getDoubleTy(*TheContext)); 631 | } 632 | 633 | Function *PrototypeAST::codegen() { 634 | std::vector Doubles(Args.size(), Type::getDoubleTy(*TheContext)); 635 | FunctionType *FT = 636 | FunctionType::get(Type::getDoubleTy(*TheContext), Doubles, false); 637 | 638 | Function *F = 639 | Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get()); 640 | unsigned Idx = 0; 641 | for (auto &Arg : F->args()) 642 | Arg.setName(Args[Idx++]); 643 | 644 | return F; 645 | } 646 | 647 | Function *FunctionAST::codegen() { 648 | auto &P = *Proto; 649 | FunctionProtos[Proto->getName()] = std::move(Proto); 650 | Function *TheFunction = getFunction(P.getName()); 651 | 652 | if (!TheFunction) 653 | return nullptr; 654 | 655 | BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction); 656 | Builder->SetInsertPoint(BB); 657 | 658 | NamedValues.clear(); 659 | for (auto &Arg : TheFunction->args()) 660 | NamedValues[std::string(Arg.getName())] = &Arg; 661 | 662 | if (Value *RetVal = Body->codegen()) { 663 | Builder->CreateRet(RetVal); 664 | 665 | verifyFunction(*TheFunction); 666 | 667 | TheFPM->run(*TheFunction); 668 | 669 | return TheFunction; 670 | } 671 | 672 | TheFunction->eraseFromParent(); 673 | return nullptr; 674 | } 675 | 676 | 677 | static void InitializeModuleAndPassManager() { 678 | TheContext = std::make_unique(); 679 | TheModule = std::make_unique("my cool jit", *TheContext); 680 | TheModule->setDataLayout(TheJIT->getDataLayout()); 681 | 682 | Builder = std::make_unique>(*TheContext); 683 | 684 | TheFPM = std::make_unique(TheModule.get()); 685 | 686 | TheFPM->add(createInstructionCombiningPass()); 687 | TheFPM->add(createReassociatePass()); 688 | TheFPM->add(createGVNPass()); 689 | TheFPM->add(createCFGSimplificationPass()); 690 | 691 | TheFPM->doInitialization(); 692 | } 693 | 694 | static void HandleDefinition() { 695 | if (auto FnAST = ParseDefinition()) { 696 | if (auto *FnIR = FnAST->codegen()) { 697 | fprintf(stderr, "Read function definition:"); 698 | FnIR->print(errs()); 699 | fprintf(stderr, "\n"); 700 | ExitOnErr(TheJIT->addModule( 701 | ThreadSafeModule(std::move(TheModule), std::move(TheContext)))); 702 | InitializeModuleAndPassManager(); 703 | } 704 | } else { 705 | getNextToken(); 706 | } 707 | } 708 | 709 | static void HandleExtern() { 710 | if (auto ProtoAST = ParseExtern()) { 711 | if (auto *FnIR = ProtoAST->codegen()) { 712 | fprintf(stderr, "Read extern: "); 713 | FnIR->print(errs()); 714 | fprintf(stderr, "\n"); 715 | FunctionProtos[ProtoAST->getName()] = std::move(ProtoAST); 716 | } 717 | } else { 718 | getNextToken(); 719 | } 720 | } 721 | 722 | static void HanldeTopLevelExpression() { 723 | if (auto FnAST = ParseTopLevelExpr()) { 724 | if (auto *FnIR = FnAST->codegen()) { 725 | auto RT = TheJIT->getMainJITDylib().createResourceTracker(); 726 | 727 | auto TSM = ThreadSafeModule(std::move(TheModule), std::move(TheContext)); 728 | ExitOnErr(TheJIT->addModule(std::move(TSM), RT)); 729 | InitializeModuleAndPassManager(); 730 | 731 | auto ExprSymbol = ExitOnErr(TheJIT->lookup("__anon_expr")); 732 | 733 | double (*FP)() = (double (*)())(intptr_t)ExprSymbol.getAddress(); 734 | fprintf(stderr, "Evaluated to %f\n", FP()); 735 | 736 | ExitOnErr(RT->remove()); 737 | } 738 | } else { 739 | getNextToken(); 740 | } 741 | } 742 | 743 | static void MainLoop() { 744 | while (true) { 745 | fprintf(stderr, "ready> "); 746 | switch (CurTok) { 747 | case tok_eof: 748 | return; 749 | case ';': 750 | getNextToken(); 751 | break; 752 | case tok_def: 753 | HandleDefinition(); 754 | break; 755 | case tok_extern: 756 | HandleExtern(); 757 | break; 758 | default: 759 | HanldeTopLevelExpression(); 760 | break; 761 | } 762 | } 763 | } 764 | 765 | #ifdef _WIN32 766 | #define DLLEXPORT __declspec(dllexport) 767 | #else 768 | #define DLLEXPORT 769 | #endif 770 | 771 | extern "C" DLLEXPORT double putchard(double X) { 772 | fputc((char)X, stderr); 773 | return 0; 774 | } 775 | 776 | extern "C" DLLEXPORT double printd(double X) { 777 | fprintf(stderr, "%f\n", X); 778 | return 0; 779 | } 780 | 781 | int main() { 782 | InitializeNativeTarget(); 783 | InitializeNativeTargetAsmPrinter(); 784 | InitializeNativeTargetAsmParser(); 785 | 786 | BinopPrecedence['<'] = 10; 787 | BinopPrecedence['+'] = 20; 788 | BinopPrecedence['-'] = 20; 789 | BinopPrecedence['*'] = 40; 790 | 791 | fprintf(stderr, "ready> "); 792 | getNextToken(); 793 | 794 | TheJIT = ExitOnErr(KaleidoscopeJIT::Create()); 795 | 796 | InitializeModuleAndPassManager(); 797 | 798 | MainLoop(); 799 | 800 | TheModule->print(errs(), nullptr); 801 | 802 | return 0; 803 | } 804 | -------------------------------------------------------------------------------- /doc/Kaleidoscope06.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope:扩展语言:用户定义运算符 2 | 3 | ## 第六章绪论 4 | 5 | 到现在,我们已经有了一种功能齐全的语言,它相当简单,但也很有用。然而,它仍然有一个很大的问题。我们的语言没有很多有用的运算符(比如除法、逻辑否定,甚至除了小于之外的任何比较)。 6 | 7 | 本教程的这一章将离开主线介绍一个副本-如何将用户定义的运算符添加到简单而漂亮的Kaleidoscope语言中。这个副本在某些方面给了我们一种简单而丑陋的语言,但同时也给了我们一种功能强大的语言。创造自己的语言的一大好处就是你可以决定什么是好的,什么是坏的。在本教程中,我们将假设其用作展示一些有趣的解析技术是好的做法。 8 | 9 | 10 | ## 用户定义运算符:理念 11 | 12 | 我们将添加到Kaleidoscope中的“运算符重载”比在C++等语言中的“运算符重载”更通用。在C++中,您只允许重新定义现有操作符:您不能以编程方式更改语法、引入新操作符、更改优先级别等。在本章中,我们将向Kaleidoscope添加此功能,这将允许用户对所支持的操作符集合进行取舍。 13 | 14 | 在这样的教程中介绍用户定义的运算符的目的是展示使用手写解析器的功能和灵活性。到目前为止,我们已经实现的解析器对大部分语法使用递归下降解析,对表达式使用运算符优先解析。详见[第2章](Kaleidoscope02.md)。使用运算符优先解析,允许程序员在语法中很容易引入新的运算符:随着JIT的运行,语法是动态可扩展的。 15 | 16 | 我们要添加的两个特定功能是可编程的一元运算符(目前,Kaleidoscope根本没有一元运算符)以及二元运算符。例如: 17 | ``` 18 | # Logical unary not. 19 | def unary!(v) 20 | if v then 21 | 0 22 | else 23 | 1; 24 | 25 | # Define > with the same precedence as <. 26 | def binary> 10 (LHS RHS) 27 | RHS < LHS; 28 | 29 | # Binary "logical or", (note that it does not "short circuit") 30 | def binary| 5 (LHS RHS) 31 | if LHS then 32 | 1 33 | else if RHS then 34 | 1 35 | else 36 | 0; 37 | 38 | # Define = with slightly lower precedence than relationals. 39 | def binary= 9 (LHS RHS) 40 | !(LHS < RHS | LHS > RHS); 41 | ``` 42 | 43 | 许多语言都渴望能够用语言本身实现它们的标准运行时库。在Kaleidoscope中,我们可以在库中实现语言的重要部分! 44 | 45 | 我们将把这些功能的实现分为两部分:实现对用户定义的二元运算符的支持和添加一元运算符。 46 | 47 | ## 用户定义的二元运算符 48 | 49 | 在我们当前的框架中,添加对用户定义的二元运算符的支持非常简单。我们将首先添加对一元/二元关键字的支持: 50 | 51 | ```c++ 52 | enum Token { 53 | ... 54 | // operators 55 | tok_binary = -11, 56 | tok_unary = -12 57 | }; 58 | ... 59 | static int gettok() { 60 | ... 61 | if (IdentifierStr == "for") 62 | return tok_for; 63 | if (IdentifierStr == "in") 64 | return tok_in; 65 | if (IdentifierStr == "binary") 66 | return tok_binary; 67 | if (IdentifierStr == "unary") 68 | return tok_unary; 69 | return tok_identifier; 70 | ``` 71 | 72 | 这只是添加了对一元和二进制关键字的词法分析器支持,就像我们在[章节](Kaleidoscope05.md)中所做的那样我们当前AST的一个优点是,我们使用二元运算符的ASCII码作为操作码来表示完全泛化的二元运算符。对于我们的扩展操作符,我们将使用相同的表示,因此我们不需要任何新的AST或解析器支持。 73 | 74 | 另一方面,我们必须能够在函数定义的“def Binary\\5”部分中表示这些新运算符的定义。到目前为止,在我们的语法中,函数定义的“name”被解析为“Prototype”类型,并解析到`PrototypeAST`AST节点。要将新的用户定义运算符表示为原型,我们必须扩展`PrototypeAST`AST节点,如下所示: 75 | 76 | ```c++ 77 | /// PrototypeAST - This class represents the "prototype" for a function, 78 | /// which captures its argument names as well as if it is an operator. 79 | class PrototypeAST { 80 | std::string Name; 81 | std::vector Args; 82 | bool IsOperator; 83 | unsigned Precedence; // Precedence if a binary op. 84 | 85 | public: 86 | PrototypeAST(const std::string &name, std::vector Args, 87 | bool IsOperator = false, unsigned Prec = 0) 88 | : Name(name), Args(std::move(Args)), IsOperator(IsOperator), 89 | Precedence(Prec) {} 90 | 91 | Function *codegen(); 92 | const std::string &getName() const { return Name; } 93 | 94 | bool isUnaryOp() const { return IsOperator && Args.size() == 1; } 95 | bool isBinaryOp() const { return IsOperator && Args.size() == 2; } 96 | 97 | char getOperatorName() const { 98 | assert(isUnaryOp() || isBinaryOp()); 99 | return Name[Name.size() - 1]; 100 | } 101 | 102 | unsigned getBinaryPrecedence() const { return Precedence; } 103 | }; 104 | ``` 105 | 106 | 基本上,除了知道原型的名称之外,我们现在还跟踪它是否是运算符,如果是,则跟踪运算符的优先级别。优先级仅用于二元运算符(正如您将在下面看到的,它不适用于一元运算符)。现在我们有了表示用户定义运算符的原型(prototype)的方法,我们需要对其进行解析: 107 | 108 | ```c++ 109 | /// prototype 110 | /// ::= id '(' id* ')' 111 | /// ::= binary LETTER number? (id, id) 112 | static std::unique_ptr ParsePrototype() { 113 | std::string FnName; 114 | 115 | unsigned Kind = 0; // 0 = identifier, 1 = unary, 2 = binary. 116 | unsigned BinaryPrecedence = 30; 117 | 118 | switch (CurTok) { 119 | default: 120 | return LogErrorP("Expected function name in prototype"); 121 | case tok_identifier: 122 | FnName = IdentifierStr; 123 | Kind = 0; 124 | getNextToken(); 125 | break; 126 | case tok_binary: 127 | getNextToken(); 128 | if (!isascii(CurTok)) 129 | return LogErrorP("Expected binary operator"); 130 | FnName = "binary"; 131 | FnName += (char)CurTok; 132 | Kind = 2; 133 | getNextToken(); 134 | 135 | // Read the precedence if present. 136 | if (CurTok == tok_number) { 137 | if (NumVal < 1 || NumVal > 100) 138 | return LogErrorP("Invalid precedence: must be 1..100"); 139 | BinaryPrecedence = (unsigned)NumVal; 140 | getNextToken(); 141 | } 142 | break; 143 | } 144 | 145 | if (CurTok != '(') 146 | return LogErrorP("Expected '(' in prototype"); 147 | 148 | std::vector ArgNames; 149 | while (getNextToken() == tok_identifier) 150 | ArgNames.push_back(IdentifierStr); 151 | if (CurTok != ')') 152 | return LogErrorP("Expected ')' in prototype"); 153 | 154 | // success. 155 | getNextToken(); // eat ')'. 156 | 157 | // Verify right number of names for operator. 158 | if (Kind && ArgNames.size() != Kind) 159 | return LogErrorP("Invalid number of operands for operator"); 160 | 161 | return std::make_unique(FnName, std::move(ArgNames), Kind != 0, 162 | BinaryPrecedence); 163 | } 164 | ``` 165 | 166 | 这些都是相当简单的解析代码,我们在过去已经看到了很多类似的代码。上述代码有趣的一部分是为二元运算符设置`FnName`的几行代码。这将为新定义的“@”运算符构建“BINARY@”之这样的名称。然后,它利用LLVM符号表中的符号名称被允许包含任何字符的事实,包含嵌入的NUL字符。 167 | 168 | 接下来要添加的有趣内容是对这些二元运算符的代码生成支持。根据我们当前的结构,这是为现有二元运算符节点添加一个默认情况的简单示例: 169 | 170 | ```c++ 171 | Value *BinaryExprAST::codegen() { 172 | Value *L = LHS->codegen(); 173 | Value *R = RHS->codegen(); 174 | if (!L || !R) 175 | return nullptr; 176 | 177 | switch (Op) { 178 | case '+': 179 | return Builder.CreateFAdd(L, R, "addtmp"); 180 | case '-': 181 | return Builder.CreateFSub(L, R, "subtmp"); 182 | case '*': 183 | return Builder.CreateFMul(L, R, "multmp"); 184 | case '<': 185 | L = Builder.CreateFCmpULT(L, R, "cmptmp"); 186 | // Convert bool 0/1 to double 0.0 or 1.0 187 | return Builder.CreateUIToFP(L, Type::getDoubleTy(TheContext), 188 | "booltmp"); 189 | default: 190 | break; 191 | } 192 | 193 | // If it wasn't a builtin binary operator, it must be a user defined one. Emit 194 | // a call to it. 195 | Function *F = getFunction(std::string("binary") + Op); 196 | assert(F && "binary operator not found!"); 197 | 198 | Value *Ops[2] = { L, R }; 199 | return Builder.CreateCall(F, Ops, "binop"); 200 | } 201 | ``` 202 | 203 | 正如您在上面看到的,新代码实际上非常简单。它只是在符号表中查找适当的运算符,并生成对它的函数调用。由于用户定义的运算符只是构建为普通函数(因为“Prototype”归根结底是一个具有正确名称的函数),所以一切都井然有序。 204 | 205 | 我们遗漏的最后一段代码是一些顶层的技巧(译者注:原文为magic): 206 | 207 | ```c++ 208 | Function *FunctionAST::codegen() { 209 | // Transfer ownership of the prototype to the FunctionProtos map, but keep a 210 | // reference to it for use below. 211 | auto &P = *Proto; 212 | FunctionProtos[Proto->getName()] = std::move(Proto); 213 | Function *TheFunction = getFunction(P.getName()); 214 | if (!TheFunction) 215 | return nullptr; 216 | 217 | // If this is an operator, install it. 218 | if (P.isBinaryOp()) 219 | BinopPrecedence[P.getOperatorName()] = P.getBinaryPrecedence(); 220 | 221 | // Create a new basic block to start insertion into. 222 | BasicBlock *BB = BasicBlock::Create(TheContext, "entry", TheFunction); 223 | ... 224 | ``` 225 | 226 | 基本上,在对函数进行代码生成之前,如果它是用户定义的运算符,我们会将其注册到优先顺序表中。这允许我们已有的二元运算符解析逻辑来处理它。由于我们正在开发一个完全通用的运算符优先解析器,这就是我们“扩展语法”需要做的全部工作。 227 | 228 | 现在我们有了有用的用户定义的二元运算符。这在很大程度上建立在我们之前为其他运算符构建的框架之上。添加一元运算符更具挑战性,因为我们还没有任何框架-让我们看看需要什么。 229 | 230 | ## 用户定义的一元运算符 231 | 232 | 因为我们目前不支持Kaleidoscope语言中的一元运算符,所以我们需要添加所有内容来支持它们。上面,我们在词法分析器中添加了对‘unary’关键字的简单支持。除此之外,我们还需要一个AST节点: 233 | 234 | ```c++ 235 | /// UnaryExprAST - Expression class for a unary operator. 236 | class UnaryExprAST : public ExprAST { 237 | char Opcode; 238 | std::unique_ptr Operand; 239 | 240 | public: 241 | UnaryExprAST(char Opcode, std::unique_ptr Operand) 242 | : Opcode(Opcode), Operand(std::move(Operand)) {} 243 | 244 | Value *codegen() override; 245 | }; 246 | ``` 247 | 248 | 到目前为止,这个AST节点非常简单和明显。它直接采用二元运算符AST节点的镜像,只是它只有一个子节点。因此,我们需要添加解析逻辑。解析一元运算符非常简单:我们将添加一个新函数来执行此操作: 249 | 250 | ```c++ 251 | /// unary 252 | /// ::= primary 253 | /// ::= '!' unary 254 | static std::unique_ptr ParseUnary() { 255 | // If the current token is not an operator, it must be a primary expr. 256 | if (!isascii(CurTok) || CurTok == '(' || CurTok == ',') 257 | return ParsePrimary(); 258 | 259 | // If this is a unary operator, read it. 260 | int Opc = CurTok; 261 | getNextToken(); 262 | if (auto Operand = ParseUnary()) 263 | return std::make_unique(Opc, std::move(Operand)); 264 | return nullptr; 265 | } 266 | ``` 267 | 268 | 我们在这里添加的语法相当简单。如果在解析主运算符时看到一元运算符,我们会将该运算符作为前缀,并将其余部分作为另一个一元运算符进行解析。这允许我们处理多个一元运算符(例如,“!!x”)。请注意,一元操作符不能像二元操作符那样具有模棱两可的解析,因此不需要优先级信息。 269 | 270 | 这个函数的问题在于,我们需要从某个地方调用ParseUnary。为此,我们将之前的ParsePrimary调用方更改为调用ParseUnary: 271 | 272 | ```c++ 273 | /// binoprhs 274 | /// ::= ('+' unary)* 275 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 276 | std::unique_ptr LHS) { 277 | ... 278 | // Parse the unary expression after the binary operator. 279 | auto RHS = ParseUnary(); 280 | if (!RHS) 281 | return nullptr; 282 | ... 283 | } 284 | /// expression 285 | /// ::= unary binoprhs 286 | /// 287 | static std::unique_ptr ParseExpression() { 288 | auto LHS = ParseUnary(); 289 | if (!LHS) 290 | return nullptr; 291 | 292 | return ParseBinOpRHS(0, std::move(LHS)); 293 | } 294 | ``` 295 | 296 | 通过这两个简单的更改,我们现在可以解析一元运算符并为它们构建AST。接下来,我们需要添加对原型的解析器支持,以解析一元运算符原型。我们使用以下内容扩展上面的二元运算符代码: 297 | 298 | ```c++ 299 | /// prototype 300 | /// ::= id '(' id* ')' 301 | /// ::= binary LETTER number? (id, id) 302 | /// ::= unary LETTER (id) 303 | static std::unique_ptr ParsePrototype() { 304 | std::string FnName; 305 | 306 | unsigned Kind = 0; // 0 = identifier, 1 = unary, 2 = binary. 307 | unsigned BinaryPrecedence = 30; 308 | 309 | switch (CurTok) { 310 | default: 311 | return LogErrorP("Expected function name in prototype"); 312 | case tok_identifier: 313 | FnName = IdentifierStr; 314 | Kind = 0; 315 | getNextToken(); 316 | break; 317 | case tok_unary: 318 | getNextToken(); 319 | if (!isascii(CurTok)) 320 | return LogErrorP("Expected unary operator"); 321 | FnName = "unary"; 322 | FnName += (char)CurTok; 323 | Kind = 1; 324 | getNextToken(); 325 | break; 326 | case tok_binary: 327 | ... 328 | ``` 329 | 330 | 与二元运算符一样,我们使用包含运算符字符的名称命名一元运算符。这在代码生成时对我们有帮助。说到这里,我们需要添加的最后一点是对一元运算符的代码生成支持。它看起来是这样的: 331 | 332 | ```c++ 333 | Value *UnaryExprAST::codegen() { 334 | Value *OperandV = Operand->codegen(); 335 | if (!OperandV) 336 | return nullptr; 337 | 338 | Function *F = getFunction(std::string("unary") + Opcode); 339 | if (!F) 340 | return LogErrorV("Unknown unary operator"); 341 | 342 | return Builder.CreateCall(F, OperandV, "unop"); 343 | } 344 | ``` 345 | 346 | 此代码类似于二元运算符的代码,但比二元运算符的代码更简单。它更简单,主要是因为它不需要处理任何预定义的运算符。 347 | 348 | ## 验证示例(译者注:原文为Kicking the tires,俚语,意思是草率检验。) 349 | 350 | 这有点令人难以置信,但通过我们在上一章中介绍的几个简单扩展,我们已经发展出一种真正意义上的语言。有了这些,我们可以做很多有趣的事情,包括I/O、数学运算和许多其他事情。例如,我们现在可以添加一个很好的排序操作符(printd定义为打印指定的值和换行符): 351 | ``` 352 | ready> extern printd(x); 353 | Read extern: 354 | declare double @printd(double) 355 | 356 | ready> def binary : 1 (x y) 0; # Low-precedence operator that ignores operands. 357 | ... 358 | ready> printd(123) : printd(456) : printd(789); 359 | 123.000000 360 | 456.000000 361 | 789.000000 362 | Evaluated to 0.000000 363 | ``` 364 | 365 | 我们还可以定义一组其他的“原始”操作,例如: 366 | ``` 367 | # Logical unary not. 368 | def unary!(v) 369 | if v then 370 | 0 371 | else 372 | 1; 373 | 374 | # Unary negate. 375 | def unary-(v) 376 | 0-v; 377 | 378 | # Define > with the same precedence as <. 379 | def binary> 10 (LHS RHS) 380 | RHS < LHS; 381 | 382 | # Binary logical or, which does not short circuit. 383 | def binary| 5 (LHS RHS) 384 | if LHS then 385 | 1 386 | else if RHS then 387 | 1 388 | else 389 | 0; 390 | 391 | # Binary logical and, which does not short circuit. 392 | def binary& 6 (LHS RHS) 393 | if !LHS then 394 | 0 395 | else 396 | !!RHS; 397 | 398 | # Define = with slightly lower precedence than relationals. 399 | def binary = 9 (LHS RHS) 400 | !(LHS < RHS | LHS > RHS); 401 | 402 | # Define ':' for sequencing: as a low-precedence operator that ignores operands 403 | # and just returns the RHS. 404 | def binary : 1 (x y) y; 405 | ``` 406 | 407 | 给定前面的IF/THEN/ELSE支持,我们还可以为I/O定义有趣的函数。例如,下面打印出一个字符,其“Density”反映传入的值:该值越低,该字符就越密集: 408 | 409 | ready> extern putchard(char); 410 | ... 411 | ready> def printdensity(d) 412 | if d > 8 then 413 | putchard(32) # ' ' 414 | else if d > 4 then 415 | putchard(46) # '.' 416 | else if d > 2 then 417 | putchard(43) # '+' 418 | else 419 | putchard(42); # '*' 420 | ... 421 | ready> printdensity(1): printdensity(2): printdensity(3): 422 | printdensity(4): printdensity(5): printdensity(9): 423 | putchard(10); 424 | **++. 425 | Evaluated to 0.000000 426 | 427 | 基于这些简单的原语操作,我们可以开始定义更有趣的东西。例如,下面是一个小函数,它确定复平面中的某个函数发散所需的迭代次数: 428 | ``` 429 | # Determine whether the specific location diverges. 430 | # Solve for z = z^2 + c in the complex plane. 431 | def mandelconverger(real imag iters creal cimag) 432 | if iters > 255 | (real*real + imag*imag > 4) then 433 | iters 434 | else 435 | mandelconverger(real*real - imag*imag + creal, 436 | 2*real*imag + cimag, 437 | iters+1, creal, cimag); 438 | 439 | # Return the number of iterations required for the iteration to escape 440 | def mandelconverge(real imag) 441 | mandelconverger(real, imag, 0, real, imag); 442 | ``` 443 | 444 | 这个“`Z=z2+c`”函数是一个美丽的小生物,它是计算[Mandelbrot Set](http://en.wikipedia.org/wiki/Mandelbrot_set).]的基础。我们的‘mandelConverge`函数返回复平面逃逸所需的迭代次数,溢出值为255。这本身并不是一个非常有用的函数,但是如果您在二维平面上绘制它的值,您可以看到Mandelbrot Set。鉴于我们在这里仅限于使用putchard,我们令人惊叹的图形输出也是有限的,但我们可以使用上面的密度绘图仪拼凑出一些东西: 445 | ``` 446 | # Compute and plot the mandelbrot set with the specified 2 dimensional range 447 | # info. 448 | def mandelhelp(xmin xmax xstep ymin ymax ystep) 449 | for y = ymin, y < ymax, ystep in ( 450 | (for x = xmin, x < xmax, xstep in 451 | printdensity(mandelconverge(x,y))) 452 | : putchard(10) 453 | ) 454 | 455 | # mandel - This is a convenient helper function for plotting the mandelbrot set 456 | # from the specified position with the specified Magnification. 457 | def mandel(realstart imagstart realmag imagmag) 458 | mandelhelp(realstart, realstart+realmag*78, realmag, 459 | imagstart, imagstart+imagmag*40, imagmag); 460 | ``` 461 | 462 | 考虑到这一点,我们可以试着画出Mandelbrot set!让我们试试看: 463 | ``` 464 | ready> mandel(-2.3, -1.3, 0.05, 0.07); 465 | *******************************+++++++++++************************************* 466 | *************************+++++++++++++++++++++++******************************* 467 | **********************+++++++++++++++++++++++++++++**************************** 468 | *******************+++++++++++++++++++++.. ...++++++++************************* 469 | *****************++++++++++++++++++++++.... ...+++++++++*********************** 470 | ***************+++++++++++++++++++++++..... ...+++++++++********************* 471 | **************+++++++++++++++++++++++.... ....+++++++++******************** 472 | *************++++++++++++++++++++++...... .....++++++++******************* 473 | ************+++++++++++++++++++++....... .......+++++++****************** 474 | ***********+++++++++++++++++++.... ... .+++++++***************** 475 | **********+++++++++++++++++....... .+++++++**************** 476 | *********++++++++++++++........... ...+++++++*************** 477 | ********++++++++++++............ ...++++++++************** 478 | ********++++++++++... .......... .++++++++************** 479 | *******+++++++++..... .+++++++++************* 480 | *******++++++++...... ..+++++++++************* 481 | *******++++++....... ..+++++++++************* 482 | *******+++++...... ..+++++++++************* 483 | *******.... .... ...+++++++++************* 484 | *******.... . ...+++++++++************* 485 | *******+++++...... ...+++++++++************* 486 | *******++++++....... ..+++++++++************* 487 | *******++++++++...... .+++++++++************* 488 | *******+++++++++..... ..+++++++++************* 489 | ********++++++++++... .......... .++++++++************** 490 | ********++++++++++++............ ...++++++++************** 491 | *********++++++++++++++.......... ...+++++++*************** 492 | **********++++++++++++++++........ .+++++++**************** 493 | **********++++++++++++++++++++.... ... ..+++++++**************** 494 | ***********++++++++++++++++++++++....... .......++++++++***************** 495 | ************+++++++++++++++++++++++...... ......++++++++****************** 496 | **************+++++++++++++++++++++++.... ....++++++++******************** 497 | ***************+++++++++++++++++++++++..... ...+++++++++********************* 498 | *****************++++++++++++++++++++++.... ...++++++++*********************** 499 | *******************+++++++++++++++++++++......++++++++************************* 500 | *********************++++++++++++++++++++++.++++++++*************************** 501 | *************************+++++++++++++++++++++++******************************* 502 | ******************************+++++++++++++************************************ 503 | ******************************************************************************* 504 | ******************************************************************************* 505 | ******************************************************************************* 506 | Evaluated to 0.000000 507 | ready> mandel(-2, -1, 0.02, 0.04); 508 | **************************+++++++++++++++++++++++++++++++++++++++++++++++++++++ 509 | ***********************++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 510 | *********************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++. 511 | *******************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++... 512 | *****************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++..... 513 | ***************++++++++++++++++++++++++++++++++++++++++++++++++++++++++........ 514 | **************++++++++++++++++++++++++++++++++++++++++++++++++++++++........... 515 | ************+++++++++++++++++++++++++++++++++++++++++++++++++++++.............. 516 | ***********++++++++++++++++++++++++++++++++++++++++++++++++++........ . 517 | **********++++++++++++++++++++++++++++++++++++++++++++++............. 518 | ********+++++++++++++++++++++++++++++++++++++++++++.................. 519 | *******+++++++++++++++++++++++++++++++++++++++....................... 520 | ******+++++++++++++++++++++++++++++++++++........................... 521 | *****++++++++++++++++++++++++++++++++............................ 522 | *****++++++++++++++++++++++++++++............................... 523 | ****++++++++++++++++++++++++++...... ......................... 524 | ***++++++++++++++++++++++++......... ...... ........... 525 | ***++++++++++++++++++++++............ 526 | **+++++++++++++++++++++.............. 527 | **+++++++++++++++++++................ 528 | *++++++++++++++++++................. 529 | *++++++++++++++++............ ... 530 | *++++++++++++++.............. 531 | *+++....++++................ 532 | *.......... ........... 533 | * 534 | *.......... ........... 535 | *+++....++++................ 536 | *++++++++++++++.............. 537 | *++++++++++++++++............ ... 538 | *++++++++++++++++++................. 539 | **+++++++++++++++++++................ 540 | **+++++++++++++++++++++.............. 541 | ***++++++++++++++++++++++............ 542 | ***++++++++++++++++++++++++......... ...... ........... 543 | ****++++++++++++++++++++++++++...... ......................... 544 | *****++++++++++++++++++++++++++++............................... 545 | *****++++++++++++++++++++++++++++++++............................ 546 | ******+++++++++++++++++++++++++++++++++++........................... 547 | *******+++++++++++++++++++++++++++++++++++++++....................... 548 | ********+++++++++++++++++++++++++++++++++++++++++++.................. 549 | Evaluated to 0.000000 550 | ready> mandel(-0.9, -1.4, 0.02, 0.03); 551 | ******************************************************************************* 552 | ******************************************************************************* 553 | ******************************************************************************* 554 | **********+++++++++++++++++++++************************************************ 555 | *+++++++++++++++++++++++++++++++++++++++*************************************** 556 | +++++++++++++++++++++++++++++++++++++++++++++********************************** 557 | ++++++++++++++++++++++++++++++++++++++++++++++++++***************************** 558 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++************************* 559 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++********************** 560 | +++++++++++++++++++++++++++++++++.........++++++++++++++++++******************* 561 | +++++++++++++++++++++++++++++++.... ......+++++++++++++++++++**************** 562 | +++++++++++++++++++++++++++++....... ........+++++++++++++++++++************** 563 | ++++++++++++++++++++++++++++........ ........++++++++++++++++++++************ 564 | +++++++++++++++++++++++++++......... .. ...+++++++++++++++++++++********** 565 | ++++++++++++++++++++++++++........... ....++++++++++++++++++++++******** 566 | ++++++++++++++++++++++++............. .......++++++++++++++++++++++****** 567 | +++++++++++++++++++++++............. ........+++++++++++++++++++++++**** 568 | ++++++++++++++++++++++........... ..........++++++++++++++++++++++*** 569 | ++++++++++++++++++++........... .........++++++++++++++++++++++* 570 | ++++++++++++++++++............ ...........++++++++++++++++++++ 571 | ++++++++++++++++............... .............++++++++++++++++++ 572 | ++++++++++++++................. ...............++++++++++++++++ 573 | ++++++++++++.................. .................++++++++++++++ 574 | +++++++++.................. .................+++++++++++++ 575 | ++++++........ . ......... ..++++++++++++ 576 | ++............ ...... ....++++++++++ 577 | .............. ...++++++++++ 578 | .............. ....+++++++++ 579 | .............. .....++++++++ 580 | ............. ......++++++++ 581 | ........... .......++++++++ 582 | ......... ........+++++++ 583 | ......... ........+++++++ 584 | ......... ....+++++++ 585 | ........ ...+++++++ 586 | ....... ...+++++++ 587 | ....+++++++ 588 | .....+++++++ 589 | ....+++++++ 590 | ....+++++++ 591 | ....+++++++ 592 | Evaluated to 0.000000 593 | ready> ^D 594 | ``` 595 | 596 | 到现在,您可能开始意识到Kaleidoscope是一种真实而强大的语言。它可能不是自相似的:),但它可以用来绘制具有自相似的东西! 597 | 598 | 至此,我们结束了本教程的“添加用户定义运算符”一章。我们已经成功地扩展了我们的语言,添加了在库中扩展语言的能力,并且我们已经展示了如何使用这一功能在Kaleidoscope中构建简单但有趣的最终用户应用程序。在这一点上,Kaleidoscope可以构建各种功能齐全的应用程序,并且可以调用有side effect的函数,但是它不能实际定义和改变变量本身。 599 | 600 | 值得注意的是,可变变量是一些语言的一个重要特性,如何在不向前端添加“SSA构造”的情况下[添加对可变变量的支持](Kaleidoscope07.md)并不是显而易见的。在下一章中,我们将介绍如何在前端不构建SSA的情况下添加可变变量。 601 | 602 | 603 | [下一步:扩展语言:可变变量/SSA构造](zh-LangImpl07.md) 604 | --------------------------------------------------------------------------------