├── KaleidoscopeJIT.h ├── README.md ├── blog ├── 0.md ├── 1.md ├── 10.md ├── 2.md ├── 3.md ├── 4.md ├── 5.md ├── 6.md ├── 7.md ├── 8.md └── 9.md ├── chapter2-Implementing-a-Parser-and-AST.cpp ├── chapter3-Code-generation-to-LLVM-IR.cpp ├── chapter4-Adding-JIT-and-Optimizer-Support.cpp ├── chapter5-Extending-the-Language-Control-Flow.cpp ├── chapter6-Extending-the-language-User-defined-Operators.cpp ├── chapter7-Extending-the-Language-Mutable-Variables.cpp ├── chapter8-Compiling-to-Object-Code.cpp ├── chapter9-Adding-Debug-Information.cpp └── clion-project └── kaleidoscope ├── .idea ├── encodings.xml ├── kaleidoscope.iml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── CMakeLists.txt ├── KaleidoscopeJIT.h ├── chapter2-Implementing-a-Parser-and-AST.cpp ├── chapter3-Code-generation-to-LLVM-IR.cpp ├── chapter4-Adding-JIT-and-Optimizer-Support.cpp ├── chapter5-Extending-the-Language-Control-Flow.cpp ├── chapter6-Extending-the-language-User-defined-Operators.cpp ├── chapter7-Extending-the-Language-Mutable-Variables.cpp ├── chapter8-Compiling-to-Object-Code.cpp ├── chapter9-Adding-Debug-Information.cpp └── cmake-build-debug ├── CMakeCache.txt ├── CMakeFiles ├── 3.13.2 │ ├── CMakeCCompiler.cmake │ ├── CMakeCXXCompiler.cmake │ ├── CMakeDetermineCompilerABI_C.bin │ ├── CMakeDetermineCompilerABI_CXX.bin │ ├── CMakeSystem.cmake │ ├── CompilerIdC │ │ ├── CMakeCCompilerId.c │ │ └── a.out │ └── CompilerIdCXX │ │ ├── CMakeCXXCompilerId.cpp │ │ └── a.out ├── CMakeDirectoryInformation.cmake ├── CMakeOutput.log ├── Makefile.cmake ├── Makefile2 ├── TargetDirectories.txt ├── clion-environment.txt ├── clion-log.txt ├── cmake.check_cache ├── feature_tests.bin ├── feature_tests.c ├── feature_tests.cxx ├── kaleidoscope.dir │ ├── CXX.includecache │ ├── DependInfo.cmake │ ├── build.make │ ├── chapter2-Implementing-a-Parser-and-AST.cpp.o │ ├── chapter6-Extending-the-language-User-defined-Operators.cpp.o │ ├── chapter8-Compiling-to-Object-Code.cpp.o │ ├── cmake_clean.cmake │ ├── depend.internal │ ├── depend.make │ ├── flags.make │ ├── link.txt │ └── progress.make └── progress.marks ├── Makefile ├── cmake_install.cmake ├── kaleidoscope └── kaleidoscope.cbp /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/STLExtras.h" 17 | #include "llvm/ADT/iterator_range.h" 18 | #include "llvm/ExecutionEngine/ExecutionEngine.h" 19 | #include "llvm/ExecutionEngine/JITSymbol.h" 20 | #include "llvm/ExecutionEngine/Orc/CompileUtils.h" 21 | #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" 22 | #include "llvm/ExecutionEngine/Orc/LambdaResolver.h" 23 | #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" 24 | #include "llvm/ExecutionEngine/RTDyldMemoryManager.h" 25 | #include "llvm/ExecutionEngine/SectionMemoryManager.h" 26 | #include "llvm/IR/DataLayout.h" 27 | #include "llvm/IR/Mangler.h" 28 | #include "llvm/Support/DynamicLibrary.h" 29 | #include "llvm/Support/raw_ostream.h" 30 | #include "llvm/Target/TargetMachine.h" 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | namespace llvm { 38 | namespace orc { 39 | 40 | class KaleidoscopeJIT { 41 | public: 42 | using ObjLayerT = LegacyRTDyldObjectLinkingLayer; 43 | using CompileLayerT = LegacyIRCompileLayer; 44 | 45 | KaleidoscopeJIT() 46 | : Resolver(createLegacyLookupResolver( 47 | ES, 48 | [this](const std::string &Name) { 49 | return findMangledSymbol(Name); 50 | }, 51 | [](Error Err) { cantFail(std::move(Err), "lookupFlags failed"); })), 52 | TM(EngineBuilder().selectTarget()), DL(TM->createDataLayout()), 53 | ObjectLayer(ES, 54 | [this](VModuleKey) { 55 | return ObjLayerT::Resources{ 56 | std::make_shared(), Resolver}; 57 | }), 58 | CompileLayer(ObjectLayer, SimpleCompiler(*TM)) { 59 | llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr); 60 | } 61 | 62 | TargetMachine &getTargetMachine() { return *TM; } 63 | 64 | VModuleKey addModule(std::unique_ptr M) { 65 | auto K = ES.allocateVModule(); 66 | cantFail(CompileLayer.addModule(K, std::move(M))); 67 | ModuleKeys.push_back(K); 68 | return K; 69 | } 70 | 71 | void removeModule(VModuleKey K) { 72 | ModuleKeys.erase(find(ModuleKeys, K)); 73 | cantFail(CompileLayer.removeModule(K)); 74 | } 75 | 76 | JITSymbol findSymbol(const std::string Name) { 77 | return findMangledSymbol(mangle(Name)); 78 | } 79 | 80 | private: 81 | std::string mangle(const std::string &Name) { 82 | std::string MangledName; 83 | { 84 | raw_string_ostream MangledNameStream(MangledName); 85 | Mangler::getNameWithPrefix(MangledNameStream, Name, DL); 86 | } 87 | return MangledName; 88 | } 89 | 90 | JITSymbol findMangledSymbol(const std::string &Name) { 91 | #ifdef _WIN32 92 | // The symbol lookup of ObjectLinkingLayer uses the SymbolRef::SF_Exported 93 | // flag to decide whether a symbol will be visible or not, when we call 94 | // IRCompileLayer::findSymbolIn with ExportedSymbolsOnly set to true. 95 | // 96 | // But for Windows COFF objects, this flag is currently never set. 97 | // For a potential solution see: https://reviews.llvm.org/rL258665 98 | // For now, we allow non-exported symbols on Windows as a workaround. 99 | const bool ExportedSymbolsOnly = false; 100 | #else 101 | const bool ExportedSymbolsOnly = true; 102 | #endif 103 | 104 | // Search modules in reverse order: from last added to first added. 105 | // This is the opposite of the usual search order for dlsym, but makes more 106 | // sense in a REPL where we want to bind to the newest available definition. 107 | for (auto H : make_range(ModuleKeys.rbegin(), ModuleKeys.rend())) 108 | if (auto Sym = CompileLayer.findSymbolIn(H, Name, ExportedSymbolsOnly)) 109 | return Sym; 110 | 111 | // If we can't find the symbol in the JIT, try looking in the host process. 112 | if (auto SymAddr = RTDyldMemoryManager::getSymbolAddressInProcess(Name)) 113 | return JITSymbol(SymAddr, JITSymbolFlags::Exported); 114 | 115 | #ifdef _WIN32 116 | // For Windows retry without "_" at beginning, as RTDyldMemoryManager uses 117 | // GetProcAddress and standard libraries like msvcrt.dll use names 118 | // with and without "_" (for example "_itoa" but "sin"). 119 | if (Name.length() > 2 && Name[0] == '_') 120 | if (auto SymAddr = 121 | RTDyldMemoryManager::getSymbolAddressInProcess(Name.substr(1))) 122 | return JITSymbol(SymAddr, JITSymbolFlags::Exported); 123 | #endif 124 | 125 | return nullptr; 126 | } 127 | 128 | ExecutionSession ES; 129 | std::shared_ptr Resolver; 130 | std::unique_ptr TM; 131 | const DataLayout DL; 132 | ObjLayerT ObjectLayer; 133 | CompileLayerT CompileLayer; 134 | std::vector ModuleKeys; 135 | }; 136 | 137 | } // end namespace orc 138 | } // end namespace llvm 139 | 140 | #endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kaleidoscope-tutorial 2 | My First Language Frontend with LLVM Tutorial in Chinese 3 | 4 | 本系列是 [My First Language Frontend with LLVM Tutorial](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html) 译文,诣在熟悉LLVM的开发流程,网上有一些翻译只有前三个部分,没有翻译全,并且都是四五年前的没有更新过。由于对于编译器的概念只停留在理论上,想从代码的角度深入理解一下编译器,希望通过这部分的练习可以帮助到我。利用国庆假期这几天,我会仔细阅读此系列文档及源码并尝试翻译和记录。 5 | 6 | 开篇:[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md) 7 | 8 | [Kaleidoscope系列第一章:新语言特性和Lexer](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/1.md) 9 | 10 | [Kaleidoscope系列第二章:实现解析器和AST](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/2.md) 11 | 12 | [Kaleidoscope系列第三章:生成LLVM中间代码IR](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/3.md) 13 | 14 | [Kaleidoscope系列第四章:添加JIT和Optimizer支持](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/4.md) 15 | 16 | [Kaleidoscope系列第五章:扩展语言—控制流](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/5.md) 17 | 18 | [Kaleidoscope系列第六章:扩展语言—用户自定义运算符](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/6.md) 19 | 20 | [Kaleidoscope系列第七章:扩展语言—可变变量](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/7.md) 21 | 22 | [Kaleidoscope系列第八章:编译为目标文件](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/8.md) 23 | 24 | [Kaleidoscope系列第九章:增加调试信息](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/9.md) 25 | 26 | [Kaleidoscope系列第十章:总结和其他技巧](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/10.md) 27 | -------------------------------------------------------------------------------- /blog/0.md: -------------------------------------------------------------------------------- 1 | ## 使用LLVM开发新语言Kaleidoscope教程 2 | 3 | **前言**: 本系列是 [My First Language Frontend with LLVM Tutorial](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html) 译文,诣在熟悉LLVM的开发流程,网上有一些翻译只有前三个部分,没有翻译全,并且都是四五年前的没有更新过。由于对于编译器的概念只停留在理论上,想从代码的角度深入理解一下编译器,希望通过这部分的练习可以帮助到我。利用国庆假期这几天,我会仔细阅读此系列文档及源码并尝试翻译和记录。 4 | 5 | **要求**: 本教程只需要了解C++语言知识,编译器的相关经验不是必需的。 6 | 7 | 本教程介绍了一种简单语言的实现,展示了它多么有趣和轻松。本文将帮助我们快速入门,运行并演示使用LLVM生成代码的具体示例。 8 | 9 | 本教程将开发一个简单的“Kaleidoscope”语言,并在连续几章中对其进行迭代构建,并展示如何逐步构建。这样一来,我们就可以涵盖一系列语言设计和LLVM特定思想,一路展示和解释其代码,并减少大量的细节分析。我们强烈建议**动手复制修改并运行代码**,以此加深对编译器实现的理解。 10 | 11 | **友情提示**: 为了专注于专门讲授编译器技术和LLVM,本教程没有展示软件工程原理的最佳实践。例如,代码普遍使用全局变量,不使用[visiters](http://en.wikipedia.org/wiki/Visitor_pattern)设计模式等,而是使事情保持简单并专注于手头的编译器实现。 12 | 13 | 本教程分为以下十章,涵盖各个主题,你可以随意跳过从感兴趣的地方开始看: 14 | 15 | * [第一章:Kaleidoscope语言和Lexer](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/1.md) 这部分展示了我们要做的基本功能。词法分析器也是构建语言解析器的第一部分,我们使用了易于理解的简单 C++词法分析器。 16 | * [第二章:实现解析器和AST](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/2.md) 有了词法分析器,我们可以讨论解析技术和基本AST构造。本章介绍了递归下降解析和运算符优先级解析。 17 | * [第三章: 生成LLVM中间代码IR](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/3.md) 在准备好AST之后,我们将展示LLVM生成IR的简便性,并展示了一种将LLVM集成到项目中的简单方法。 18 | * [第四章: 添加JIT和Optimizer支持](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/4.md) LLVM的一大优点是它对JIT编译的支持,因此我们将深入探讨它,并展示添加JIT支持所需的三行内容。后面的章节介绍了如何生成.o文件。 19 | * [第五章: 扩展语言---控制流](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/5.md) 随着基本语言的启动和运行,我们展示了如何通过控制流操作(“ if”语句和“ for”循环)进行扩展。这使我们有机会讨论SSA的构建和控制流程。 20 | * [第六章: 扩展语言---用户定义运算符](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/6.md) 本章扩展了语言,使用户可以定义任意一元和二进制运算符并具有相应的优先级。这使我们可以将很大一部分“语言”构建为库例程。 21 | * [第七章: 扩展语言---可变变量](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/7.md) 本章节讨论如何用赋值语句添加用户自定义地本地变量。有趣的是,构造SSA在LLVM是相当简单的,但是LLVM并不要求你的前端来构造SSA结构。 22 | * [第八章: 编译为目标代码](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/8.md) 本章介绍如何获取LLVM IR并将其编译为目标代码,就像静态编译器一样。 23 | * [第九章: 增加调试信息](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/9.md) 一种真正的语言需要支持调试器,因此我们添加了调试信息,该信息允许在Kaleidoscope函数中设置断点,输出参数变量和调用函数! 24 | * [第十章: 总结和其他技巧](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/10.md) 本章通过讨论扩展语言的方式来总结本系列,并包括指向“special topics”的信息的指针,例如添加垃圾收集支持、异常处理、调试和对“spaghetti stacks”的支持等。 25 | 26 | 在本教程结束时,我们将编写不超过1000行(除去注释和空行)代码。借助少量的代码,我们将为一个普通的语言构建一个功能齐全的小型编译器,其中包括手写词法分析器,解析器,AST,以及代码生成(包括静态编译和JIT编译)。这种扩展充分证明了LLVM的优势,并说明了为什么LLVM被众多语言设计人员和其他研究高性能代码生成的人所喜爱。 27 | 28 | --- 29 | 30 | 参考:[My First Language Frontend with LLVM Tutorial](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html) 31 | -------------------------------------------------------------------------------- /blog/1.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第一章:新语言特性和Lexer 2 | 3 | 本文是 [使用LLVM开发新语言Kaleidoscope教程] (https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md) 系列第一章,主要介绍Kaleidoscope语言特性和词法分析器的构建。 4 | 5 | **Kaleidoscope语言特性** 6 | 7 | 本教程以一种名为“[Kaleidoscope](http://en.wikipedia.org/wiki/Kaleidoscope)”(google翻译为万花筒,源自“美丽,形式和视野”)的玩具语言进行开发。Kaleidoscope是一种过程语言,可让我们轻松定义函数,使用条件语句,数学表达式等。在本教程中,我们将扩展Kaleidoscope以支持 if/then/else语句,for循环,用户定义运算符,支持使用JIT进行简单的命令行界面编译、调试等。 8 | 9 | 再次说明,我们希望使设计语言保持简单,因此Kaleidoscope中唯一的数据类型是64位浮点类型(在C语言中为“ double”)。这样,所有值都隐式地具有双精度,并且该语言不需要类型声明。这为该语言提供了一种非常不错且简单的语法。例如,以下简单示例计算[斐波纳契数:](http://en.wikipedia.org/wiki/Fibonacci_number) 10 | 11 | ``` 12 | # Compute the x'th fibonacci number. 13 | def fib(x) 14 | if x < 3 then 15 | 1 16 | else 17 | fib(x-1)+fib(x-2) 18 | # This expression will compute the 40th number. 19 | fib(40) 20 | ``` 21 | 22 | 我们还允许Kaleidoscope调用标准库函数,因为LLVM JIT使得此操作非常容易。这意味着我们可以在使用函数之前使用'extern'关键字定义一个函数(这对于相互递归的函数也很有用)。例如: 23 | 24 | ``` 25 | extern sin(arg); 26 | extern cos(arg); 27 | extern atan2(arg1 arg2); 28 | 29 | atan2(sin(.4), cos(42)) 30 | ``` 31 | 32 | 第6章中提供了一个更有趣的示例,其中我们编写了一个迷你Kaleidoscope应用程序,该应用程序以不同的放大倍数[显示Mandelbrot集](https://www.tuhaoxin.cn/articles/2019/10/02/1570020144718.html)。 33 | 34 | 接下来我们开始深入探讨这种语言的实现。 35 | 36 | **词法分析器** 37 | 38 | 在实现语言方面,首先需要的是处理文本文件并识别其内容。传统方法是使用“[词法分析器](http://en.wikipedia.org/wiki/Lexical_analysis)”(又称“扫描器”)将输入分解为“token”。词法分析器返回的每个token都包含token代码和潜在的一些元数据(例如数字的数值等)。首先,我们定义以下token: 39 | 40 | ```cpp 41 | // The lexer returns tokens [0-255] if it is an unknown character, otherwise one 42 | // of these for known things. 43 | enum Token { 44 | tok_eof = -1, 45 | 46 | // commands 47 | tok_def = -2, 48 | tok_extern = -3, 49 | 50 | // primary 51 | tok_identifier = -4, 52 | tok_number = -5, 53 | }; 54 | 55 | static std::string IdentifierStr; // Filled in if tok_identifier 56 | static double NumVal; // Filled in if tok_number 57 | ``` 58 | 59 | 我们的词法分析器返回的每个token要么是Token枚举类型中的某值之一,要么是“未知”字符(如“ +”),并以其ASCII值返回。如果当前token是标识符,则 `IdentifierStr` 全局变量将保存标识符的名称。如果当前标记是数字(如1.0),则 `NumVal` 保留其值。为了简单起见,我们使用全局变量,但这不是真正的语言实现的最佳选择。 60 | 61 | 词法分析器实际由 `gettok` 的函数实现。`gettok` 调用该函数以从标准输入返回下一个标记。其定义开始于: 62 | 63 | ```cpp 64 | /// gettok - Return the next token from standard input. 65 | static int gettok() { 66 | static int LastChar = ' '; 67 | 68 | // Skip any whitespace. 69 | while (isspace(LastChar)) 70 | LastChar = getchar(); 71 | ``` 72 | 73 | `gettok` 通过调用C中 `getchar()` 函数从标准输入一次读取一个字符来工作。它在识别到它们后就删除它们,并将最后读取但未处理的字符存储在LastChar中。它要做的第一件事是忽略token之间的空格。该功能主要由while循环完成。 74 | 75 | 接下来 `gettok` 要做的是识别标识符和特定的关键字,例如 “def”。Kaleidoscope通过以下简单循环完成此操作: 76 | 77 | ```cpp 78 | if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]* 79 | IdentifierStr = LastChar; 80 | while (isalnum((LastChar = getchar()))) 81 | IdentifierStr += LastChar; 82 | 83 | if (IdentifierStr == "def") 84 | return tok_def; 85 | if (IdentifierStr == "extern") 86 | return tok_extern; 87 | return tok_identifier; 88 | } 89 | ``` 90 | 91 | 请注意,此代码在 `IdentifierStr` 对标识符进行词法化时都会设置成全局值。另外,由于语言关键字是由同一循环匹配的,因此我们在此对它们进行内联处理。对于处理数值也是类似的: 92 | 93 | ```cpp 94 | if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+ 95 | std::string NumStr; 96 | do { 97 | NumStr += LastChar; 98 | LastChar = getchar(); 99 | } while (isdigit(LastChar) || LastChar == '.'); 100 | 101 | NumVal = strtod(NumStr.c_str(), 0); 102 | return tok_number; 103 | } 104 | ``` 105 | 106 | 这是用于处理输入的非常简单的代码。从输入读取数值时,我们使用C中 `strtod` 函数将其转换为存储在中的数值 `NumVal`。请注意,这并没有进行足够的错误检查:它将错误地读取“ 1.23.45.67”,并像处理“ 1.23”一样处理它。当然,我们可以随意更改它! 107 | 108 | 接下来我们处理注释: 109 | 110 | ```cpp 111 | if (LastChar == '#') { 112 | // Comment until end of line. 113 | do 114 | LastChar = getchar(); 115 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 116 | 117 | if (LastChar != EOF) 118 | return gettok(); 119 | } 120 | ``` 121 | 122 | 我们通过跳到行尾来处理注释,然后返回下一个标记。最后,如果输入与以上情况之一不匹配,则该输入可能是运算符,例如“ +”,或者是文件结尾。这些使用以下代码处理: 123 | 124 | ```cpp 125 | // Check for end of file. Don't eat the EOF. 126 | if (LastChar == EOF) 127 | return tok_eof; 128 | 129 | // Otherwise, just return the character as its ascii value. 130 | int ThisChar = LastChar; 131 | LastChar = getchar(); 132 | return ThisChar; 133 | } 134 | ``` 135 | 136 | 这样,我们就拥有了用于基本Kaleidoscop语言的完整词法分析器(该词法分析的完整代码清单可在本教程的[下一章](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/2.md)中找到)。接下来,我们将[构建一个简单的解析器,使用它来构建抽象语法树](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/2.md)。当我们有了它时,我们将包括一个驱动程序,以便我们可以同时使用lexer和解析器。 137 | 138 | ---- 139 | 140 | 参考:[Kaleidoscope Introduction and the Lexer](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl01.html) 141 | -------------------------------------------------------------------------------- /blog/10.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第十章:总结和其他技巧 2 | 3 | 本文是[使用 LLVM 开发新语言 Kaleidoscope 教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)系列第十章,对 Kaleidoscope 开发过程进行总结,分析可能使用到的其他技巧。 4 | 5 | ### 教程总结 6 | 7 | 欢迎来到“[使用 LLVM 开发新语言 Kaleidoscope 教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)”教程的最后一章。在本教程的过程中,我们已经将 Kaleidoscope 这种小语言从一种无用的玩具发展为一种半有趣(但可能仍然无用)的玩具语言。 8 | 9 | 有趣的是,我们已经走了多远,花了很少的代码。我们构建了整个词法分析器,解析器,AST,代码生成器,交互式运行循环(带有 JIT!),并在独立的可执行文件中发出了调试信息-全部都在 1000 行以下(除去注释和空行)代码中。 10 | 11 | 我们的小语言支持几个有趣的功能:它支持用户定义的二元和一元运算符,它使用 JIT 编译进行即时运行,并且支持带有 SSA 构造的一些控制流构造。 12 | 13 | 本教程的部分思想是向你展示定义,构建和使用语言的过程多么容易和有趣。构建编译器不必是一个令人恐惧或神秘的过程!既然您已经了解了一些基础知识,那么我强烈建议你使用代码并对其进行破解。例如,尝试添加: 14 | 15 | * **全局变量**-尽管全局变量在现代软件工程中具有可疑的价值,但在将诸如 Kaleidoscope 编译器本身之类的快速小技巧汇集在一起时,它们通常很有用。幸运的是,我们当前的设置使添加全局变量变得非常容易:只需进行值查找检查,以在拒绝前在全局变量符号表中查看未解析的变量是否存在。要创建新的全局变量,请创建 LLVM `GlobalVariable` 类的实例。 16 | * **类型变量**-Kaleidoscope 目前仅支持 double 类型的变量。这给该语言带来了很好的优雅,因为仅支持一种类型就意味着您不必指定类型。不同的语言有不同的处理方式。最简单的方法是要求用户为每个变量定义指定类型,并将变量的类型及其 `Value *` 记录在符号表中。 17 | * **数组,结构,向量等**-添加类型后,就可以以各种有趣的方式开始扩展类型系统。简单数组非常容易,对于许多不同的应用程序非常有用。添加它们主要是学习 LLVM [getelementptr](https://llvm.org/docs/tutorial/LangRef.html#getelementptr-instruction) 指令的工作方式的练习:它是如此的[精巧](https://llvm.org/docs/tutorial/LangRef.html#getelementptr-instruction)/非常规,详情请见 [FAQ](https://llvm.org/docs/tutorial/GetElementPtr.html)! 18 | * **标准运行时**-我们当前的语言允许用户访问任意外部函数,并且我们将其用于“打印”和“ putchard”之类的事情。当您扩展语言以添加更高级别的结构时,如果将这些结构降低为对语言提供的运行时的调用,则通常最有意义。例如,如果将哈希表添加到该语言中,则可能需要将例程添加到运行时中,而不是始终将它们内联。 19 | * **内存管理**-当前,我们只能在万花筒中访问堆栈。能够通过调用标准 libc malloc / free 接口或使用垃圾收集器来分配堆内存也将很有用。如果您想使用垃圾收集,请注意 LLVM 完全支持 [Accurate Garbage Collection](https://llvm.org/docs/tutorial/GarbageCollection.html),包括移动对象并需要扫描/更新堆栈的算法。 20 | * **异常处理支持**-LLVM 支持 [零成本异常的](https://llvm.org/docs/tutorial/ExceptionHandling.html)生成,该[异常](https://llvm.org/docs/tutorial/ExceptionHandling.html)可与其他语言编译的代码互操作。您还可以通过隐式使每个函数返回错误值并对其进行检查来生成代码。你还可以显式使用 setjmp / longjmp。有很多不同的方法可以去这里。 21 | * **面向对象,泛型,数据库访问,复数,几何编程等……**-确实,您可以为该语言添加疯狂的功能。 22 | * **不寻常的领域**-我们一直在讨论将 LLVM 应用到许多人感兴趣的领域:为特定语言构建编译器。但是,还有许多其他领域可以使用通常不考虑的编译器技术。例如,LLVM 已用于实现 OpenGL 图形加速,将 C ++ 代码转换为 ActionScript 以及许多其他可爱而聪明的事情。也许你将是第一个使用 LLVM 将正则表达式解释器编译为本机代码 JIT 的! 23 | 24 | 玩得开心-尝试做疯狂和不寻常的事情。建立一种像其他人一样的语言,要比尝试一些疯狂的事情或看看墙外的东西要有趣得多。如果你遇到困难或想要讨论它,请随时向 [llvm-dev 邮件列表](http://lists.llvm.org/mailman/listinfo/llvm-dev)发送电子邮件:里面有很多对语言感兴趣并且经常乐于助人的人。 25 | 26 | 在结束本教程之前,再聊聊生成 LLVM IR 的一些“技巧”。这些是一些可能并不明显的更细微的事情,但是如果你想利用 LLVM 的功能,它们将非常有用。 27 | 28 | ### LLVM IR 特性 29 | 30 | 关于 LLVM IR 格式中的代码,我们有两个常见问题-现在就让我们解决这些问题吧! 31 | 32 | #### 目标独立 33 | 34 | Kaleidoscope 就是“便携式语言”的一个例子:用 Kaleidoscope 编写的任何程序都可以在其运行的任何目标上以相同的方式工作。许多其他语言都具有此属性,例如 lisp,Java,haskell,JavaScript,python 等(请注意,尽管这些语言是可移植的,但并不是所有的库都可以)。 35 | 36 | LLVM 的一个优点是它通常能够保持 IR 中目标的独立性:你可以将 LLVM IR 用于 Kaleidoscope 编译的程序,并在 LLVM 支持的任何目标上运行它,甚至生成 C 代码并在任何 LLVM 本身支持的目标上编译。你可以轻易地看出 Kaleidoscope 编译器会生成与目标无关的代码,因为它在生成代码时从不查询任何特定于目标的信息。 37 | 38 | LLVM 为代码提供了一种紧凑的,与目标无关的表示形式,这一事实使很多人兴奋。不幸的是,这些人在询问有关语言可移植性的问题时通常会想到 C 或 C 族的语言。我说“不幸的是”,因为除了附带提供源代码外,实际上没有办法使(完全通用的)C 代码具有可移植性(当然,C 源代码实际上也通常不是可移植的-曾经移植过很老应用程序从 32 位到 64 位?)。 39 | 40 | C 的问题(再次是完全笼统的问题)在于,它对目标的特定假设负担沉重。作为一个简单的示例,预处理器在处理输入文本时通常会破坏性地从代码中删除目标独立性: 41 | 42 | ```cpp 43 | #ifdef __i386__ 44 | int X = 1; 45 | #else 46 | int X = 42; 47 | #endif 48 | ``` 49 | 50 | 尽管可以针对此类问题设计越来越复杂的解决方案,但无法以比交付实际源代码更好的方式完全解决问题。 51 | 52 | 就是说,有一些有趣的 C 子集可以移植。如果您愿意将基本类型固定为固定大小(例如 int = 32 位,而 long = 64 位),则不必担心 ABI 与现有二进制文件的兼容性,并愿意放弃其他一些次要功能,你可以拥有可移植的代码。这对于诸如内核内语言之类的专用领域可能是有意义的。 53 | 54 | #### 安全保证 55 | 56 | 上面的许多语言也是“安全”语言:用 Java 编写的程序不可能破坏其地址空间并导致进程崩溃(假设 JVM 没有错误)。安全是一个有趣的属性,需要将语言设计,运行时支持以及经常的操作系统支持结合在一起。 57 | 58 | 当然可以在 LLVM 中实现安全语言,但是 LLVM IR 本身并不保证安全。LLVM IR 允许不安全的指针强制转换,释放错误后使用,缓冲区超限以及其他各种问题。安全需要作为 LLVM 之上的一层来实现,方便地,几个小组对此进行了调查。如果你对更多详细信息感兴趣,请在 [llvm-dev 邮件列表中](http://lists.llvm.org/mailman/listinfo/llvm-dev)询问。 59 | 60 | #### 61 | 62 | LLVM 的一件事使许多人无法接受,它不能在一个系统中解决世界上所有的问题。一个具体的困扰是人们认为 LLVM 无法执行特定于语言的高级优化:LLVM“丢失了太多信息”。以下是对此的一些观察: 63 | 64 | 首先,你是对的,LLVM 确实会丢失信息。例如,在撰写本文时,没有办法在 LLVM IR 中区分 SSA 值是来自 ILP32 机器上的 C `int` 还是 C `long`(除调试信息以外)。两者都被编译为 `i32` 值,并且有关其来源的信息也丢失了。这里更普遍的问题是 LLVM 类型系统使用“结构等效”而不是“名称等效”。让你惊讶的另一个地方是,如果你在高级语言中有两种类型具有相同的结构(例如,两个具有单个 int 字段的不同结构):这些类型将被编译为单个 LLVM 类型,并且这是不可能的告诉它来自哪里。 65 | 66 | 其次,虽然 LLVM 确实会丢失信息,但是 LLVM 并不是固定的目标:我们将继续以许多不同的方式来增强和改进它。除了添加新功能(LLVM 并不总是支持异常或调试信息)之外,我们还扩展了 IR 以捕获重要信息以进行优化(例如,参数是符号扩展还是零扩展,有关指针别名的信息,等等)。许多增强功能是用户驱动的:人们希望 LLVM 包含某些特定功能,因此他们继续进行扩展。 67 | 68 | 第三,添加特定于语言的优化是 _可能且容易的_ ,并且在执行方法方面有很多选择。作为一个简单的示例,可以很容易地添加特定于语言的优化过程,以“了解”有关为某种语言编译的代码的信息。对于 C 系列,有一个优化 pass 就可以“了解”标准 C 库函数。如果在 main()中调用 `exit(0)` ,它将知道将其优化为 `return 0;` 是安全的,因为 C 指定了 `exit` 函数的作用。 69 | 70 | 除了简单的库知识之外,还可以将各种其他特定于语言的信息嵌入到 LLVM IR 中。如果你有特定需求并遇到麻烦,请将该主题放在 llvm-dev 列表中。在最坏的情况下,你始终可以将 LLVM 视为“智障代码生成器”,并在特定于语言的 AST 上实现你希望在前端进行的高级优化。 71 | 72 | ### 其他技巧 73 | 74 | 在使用 LLVM 或使用 LLVM 后,你会发现许多有用的提示和技巧,这些乍看之下并不明显。本节将讨论其中的一些问题,而不是让所有人重新发现它们。 75 | 76 | #### 实现可移植的 offsetof / sizeof 77 | 78 | 如果你试图使编译器生成的代码“独立于目标”,那么将会发生的一件有趣的事情是,你通常需要知道某种 LLVM 类型的大小或 llvm 结构中某些字段的偏移量。例如,你可能需要将类型的大小传递给分配内存的函数。 79 | 80 | 不幸的是,这在各个目标之间变化很大:例如,指针的宽度对于目标来说是微不足道的。但是,有一种 [clever way to use the getelementptr instruction](http://nondot.org/sabre/LLVMNotes/SizeOf-OffsetOf-VariableSizedStructs.txt),使您能够以可移植的方式进行计算。 81 | 82 | #### 垃圾回收堆栈框架 83 | 84 | 某些语言通常希望显式管理其堆栈框架,以便对其进行垃圾收集或轻松实现闭包。与显式堆栈框架相比,通常有更好的方法来实现这些功能,但是 [LLVM 确实支持它们](http://nondot.org/sabre/LLVMNotes/ExplicitlyManagedStackFrames.txt)。它要求你的前端将代码转换为 [Continuation Passing Style](http://en.wikipedia.org/wiki/Continuation-passing_style),并使用 tail calls (LLVM 也支持 tail calls)。 85 | 86 | --- 87 | 88 | 参考: [Kaleidoscope: Conclusion and other useful LLVM tidbits](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl10.html) 89 | -------------------------------------------------------------------------------- /blog/2.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第二章:实现解析器和AST 2 | 3 | 本文是[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)系列第二章,主要实现Kaleidoscope语言的语法解析并生成AST的功能。 4 | 5 | ### 第二章简介 6 | 7 | 欢迎来到“[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)”教程的第二章。本章向我们展示如何使用[第一章](https://www.tuhaoxin.cn/articles/2019/10/01/1569940099352.html)中构建的词法[分析器](http://en.wikipedia.org/wiki/Parsing)为我们的Kaleidoscope语言构建完整的[解析器](http://en.wikipedia.org/wiki/Parsing)。有了解析器后,我们将定义并构建一个[抽象语法树](http://en.wikipedia.org/wiki/Abstract_syntax_tree)(AST)。 8 | 9 | 我们将构建的解析器使用[递归下降解析](http://en.wikipedia.org/wiki/Recursive_descent_parser)和[运算符优先解析的组合](http://en.wikipedia.org/wiki/Operator-precedence_parser)来解析Kaleidoscope语言(后者用于二进制表达式,前者用于其他所有内容)。在进行解析之前,让我们先讨论一下解析器的输出:抽象语法树。 10 | 11 | ### 抽象语法树 (AST) 12 | 13 | 一段程序的抽象语法树很容易在接下来的阶段编译器(比如:代码生成阶段)翻译成机器码。我们通常喜欢用一种对象来构建语言,毫无疑问,抽象语法树是最贴近我们要求的模型。我们将从表达式开始: 14 | 15 | ```cpp 16 | /// ExprAST - Base class for all expression nodes. 17 | class ExprAST { 18 | public: 19 | virtual ~ExprAST() {} 20 | }; 21 | 22 | /// NumberExprAST - Expression class for numeric literals like "1.0". 23 | class NumberExprAST : public ExprAST { 24 | double Val; 25 | 26 | public: 27 | NumberExprAST(double Val) : Val(Val) {} 28 | }; 29 | ``` 30 | 31 | 上面的代码显示了ExprAST基类的定义和一个用于数字文字的子类的定义。关于此代码,需要注意的重要一点是NumberExprAST类将文字的数字值捕获为实例变量。这使编译器的后续阶段可以知道所存储的数值是什么。 32 | 33 | 现在,我们仅创建AST,因此在它们上没有有用的方法。例如,添加一个虚拟方法来方便地打印代码。以下是Kaleidoscope中其他的AST节点的定义: 34 | 35 | ```cpp 36 | /// VariableExprAST - Expression class for referencing a variable, like "a". 37 | class VariableExprAST : public ExprAST { 38 | std::string Name; 39 | 40 | public: 41 | VariableExprAST(const std::string &Name) : Name(Name) {} 42 | }; 43 | 44 | /// BinaryExprAST - Expression class for a binary operator. 45 | class BinaryExprAST : public ExprAST { 46 | char Op; 47 | std::unique_ptr LHS, RHS; 48 | 49 | public: 50 | BinaryExprAST(char op, std::unique_ptr LHS, 51 | std::unique_ptr RHS) 52 | : Op(op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 53 | }; 54 | 55 | /// CallExprAST - Expression class for function calls. 56 | class CallExprAST : public ExprAST { 57 | std::string Callee; 58 | std::vector> Args; 59 | 60 | public: 61 | CallExprAST(const std::string &Callee, 62 | std::vector> Args) 63 | : Callee(Callee), Args(std::move(Args)) {} 64 | }; 65 | ``` 66 | 67 | 以上代码要做的事情非常简单:变量节点捕获变量名,二元运算符节点捕获其操作码(例如'+'),函数调用节点捕获函数名以及任何参数表达式的列表。我们的AST优势是它捕获了语言功能,而没有关注语言的具体语法细节。请注意,这里没有关于二元运算符,词法结构等的优先级的讨论。 68 | 69 | 对于我们的基本语言,这些都是我们将定义的所有表达节点。因为它没有条件控制流,所以它不是图灵完备的。我们将在以后的文章中解决。我们现在需要做两件事情,一件是实现在Kaleidoscope中调用函数,另一件是记录函数体的本身。 70 | 71 | ```cpp 72 | /// PrototypeAST - This class represents the "prototype" for a function, 73 | /// which captures its name, and its argument names (thus implicitly the number 74 | /// of arguments the function takes). 75 | class PrototypeAST { 76 | std::string Name; 77 | std::vector Args; 78 | 79 | public: 80 | PrototypeAST(const std::string &name, std::vector Args) 81 | : Name(name), Args(std::move(Args)) {} 82 | 83 | const std::string &getName() const { return Name; } 84 | }; 85 | 86 | /// FunctionAST - This class represents a function definition itself. 87 | class FunctionAST { 88 | std::unique_ptr Proto; 89 | std::unique_ptr Body; 90 | 91 | public: 92 | FunctionAST(std::unique_ptr Proto, 93 | std::unique_ptr Body) 94 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 95 | }; 96 | ``` 97 | 98 | 在Kaleidoscope中,函数仅以其参数数量来输入。由于所有值都是双精度浮点数,因此每个参数的类型都不需要存储在任何地方。在现代计算机语言语言中,`ExprAST`类应当有一个记录类型的变量。 99 | 100 | 有了以上类型后,我们就可以讨论在Kaleidoscope中如何解析表达式和函数体了。 101 | 102 | ### 解析器基础 103 | 104 | 现在我们有了AST,我们需要定义解析器代码来解析它。这里的想法是我们想要将类似 `x + y`(由词法分析器作为三个标记返回)的内容解析为可以通过如下调用生成AST: 105 | 106 | ```cpp 107 | auto LHS = std::make_unique("x"); 108 | auto RHS = std::make_unique("y"); 109 | auto Result = std::make_unique('+', std::move(LHS), 110 | std::move(RHS)); 111 | ``` 112 | 113 | 为此,我们将从定义一些基本的辅助程序开始: 114 | 115 | ```cpp 116 | /// CurTok/getNextToken - Provide a simple token buffer. CurTok is the current 117 | /// token the parser is looking at. getNextToken reads another token from the 118 | /// lexer and updates CurTok with its results. 119 | static int CurTok; 120 | static int getNextToken() { 121 | return CurTok = gettok(); 122 | } 123 | ``` 124 | 125 | 这在词法分析器周围实现了一个简单的token缓冲区。这使我们可以预测词法分析器将返回什么。解析器中的每个函数将假定CurTok是需要解析的当前token。 126 | 127 | ```cpp 128 | /// LogError* - These are little helper functions for error handling. 129 | std::unique_ptr LogError(const char *Str) { 130 | fprintf(stderr, "LogError: %s\n", Str); 131 | return nullptr; 132 | } 133 | std::unique_ptr LogErrorP(const char *Str) { 134 | LogError(Str); 135 | return nullptr; 136 | } 137 | ``` 138 | 139 | 这些 `LogError` 部分是我们的解析器用来处理错误的简单程序。我们的解析器中的错误恢复并不是最好的方法,并且不是对用户友好的方法,但是对于我们的教程而言,这已经足够了。这些错误处理例子使我们处理具有各种返回类型的例程中的错误更加容易:它们始终返回null。 140 | 141 | 使用这些基本的辅助函数,我们便可以实现语法的第一部分:数字。 142 | 143 | ### 解析基本表达式 144 | 145 | 我们从数字开始,因为它们是最容易处理的。对于语法中的每个产生式,我们将定义一个解析该产生式的函数。对于数字,我们有: 146 | 147 | ```cpp 148 | /// numberexpr ::= number 149 | static std::unique_ptr ParseNumberExpr() { 150 | auto Result = llvm::make_unique(NumVal); 151 | getNextToken(); // consume the number 152 | return std::move(Result); 153 | } 154 | ``` 155 | 156 | 此例程非常简单:它期望在当前token是 `tok_number` token时被调用。它采用当前数字值,创建一个 `NumberExprAST` 节点,并将词法分析器移至下一个标记,最后返回。 157 | 158 | 这有一些有趣的方面。最重要的是,该例程将删去与该生成物相对应的所有token,并返回准备好下一个token(不属于语法生成的一部分)的词法分析器缓冲区。这是用于递归下降解析器的相当标准的方法。举一个更好的例子,括号运算符的定义如下: 159 | 160 | ```cpp 161 | /// parenexpr ::= '(' expression ')' 162 | static std::unique_ptr ParseParenExpr() { 163 | getNextToken(); // eat (. 164 | auto V = ParseExpression(); 165 | if (!V) 166 | return nullptr; 167 | 168 | if (CurTok != ')') 169 | return LogError("expected ')'"); 170 | getNextToken(); // eat ). 171 | return V; 172 | } 173 | ``` 174 | 175 | 此函数说明了有关解析器的几个有意思的地方: 176 | 177 | 1)它显示了我们如何使用LogError例程。调用此函数时,该函数期望当前标记是一个 `(` 标记,但是在解析该子表达式之后,可能没有 `)` 等待。例如,如果用户键入 `(4x` 而不是 `(4)`,则解析器将发出错误。因为可能发生错误,所以解析器需要一种指示错误发生的方法:在我们的解析器中,我们返回错误时为null。 178 | 179 | 2)该函数的另一个有意思的方面是它通过调用使用递归 `ParseExpression`(我们很快就会看到 `ParseExpression` 可以调用 `ParseParenExpr` )。它之所以强大是因为它使我们能够处理递归语法,并使每个产生式都非常简单。请注意,括号不会导致AST节点本身的构造。虽然我们可以这样做,但是括号最重要的作用是引导解析器并提供分组。一旦解析器构造了AST,就不需要括号了。 180 | 181 | 下一个简单的过程是用于处理变量引用和函数调用: 182 | 183 | ```cpp 184 | /// identifierexpr 185 | /// ::= identifier 186 | /// ::= identifier '(' expression* ')' 187 | static std::unique_ptr ParseIdentifierExpr() { 188 | std::string IdName = IdentifierStr; 189 | 190 | getNextToken(); // eat identifier. 191 | 192 | if (CurTok != '(') // Simple variable ref. 193 | return llvm::make_unique(IdName); 194 | 195 | // Call. 196 | getNextToken(); // eat ( 197 | std::vector> Args; 198 | if (CurTok != ')') { 199 | while (1) { 200 | if (auto Arg = ParseExpression()) 201 | Args.push_back(std::move(Arg)); 202 | else 203 | return nullptr; 204 | 205 | if (CurTok == ')') 206 | break; 207 | 208 | if (CurTok != ',') 209 | return LogError("Expected ')' or ',' in argument list"); 210 | getNextToken(); 211 | } 212 | } 213 | 214 | // Eat the ')'. 215 | getNextToken(); 216 | 217 | return llvm::make_unique(IdName, std::move(Args)); 218 | } 219 | ``` 220 | 221 | 该例程遵循与其他例程相同的样式。(如果当前token是 `tok_identifier` 令牌,则期望被调用)。它还具有递归和错误处理功能。一个有趣的方面是,它使用 _超前_ 方法判断来确定当前标识符是独立变量引用还是函数调用表达式。它通过检查标识符后的token是否为 `(` token,并根据情况构造一个 `VariableExprAST` 或 `CallExprAST` 节点来处理此问题。 222 | 223 | 现在,我们已经有了所有简单的表达式解析逻辑,我们可以定义一个辅助函数,将其封装以便调用。我们将此类表达式称为“基本”表达式,其原因[在本教程的后面部分](https://www.tuhaoxin.cn/articles/2019/10/02/1570020144718.html)将变得更加清楚。为了解析任意表达式,我们需要确定它是哪种表达式: 224 | 225 | ```cpp 226 | /// primary 227 | /// ::= identifierexpr 228 | /// ::= numberexpr 229 | /// ::= parenexpr 230 | static std::unique_ptr ParsePrimary() { 231 | switch (CurTok) { 232 | default: 233 | return LogError("unknown token when expecting an expression"); 234 | case tok_identifier: 235 | return ParseIdentifierExpr(); 236 | case tok_number: 237 | return ParseNumberExpr(); 238 | case '(': 239 | return ParseParenExpr(); 240 | } 241 | } 242 | ``` 243 | 244 | 通过基本表达式解析器,我们可以明白为什么我们要使用 `CurTok` 了,这里用了前置判断来选择并调用解析器。 245 | 246 | 现在已经处理了基本表达式,我们需要处理二进制表达式,它们有点复杂。 247 | 248 | ### 解析二元表达式 249 | 250 | 二进制表达式通常很难确定其实际意义,因此很难解析。例如,当给定字符串 ` x + y * z` 时,解析器可以选择将其解析为 `(x + y)* z` 或 `x + (y * z)` 。使用数学上的通用定义,我们希望是按照后面这种进行解析,因为 `*` (乘法)的 _优先级_ 高于 `+`(加法)的 _优先级_。 251 | 252 | 有很多方法可以解决此问题,但是一种优雅而有效的方法是使用 [Operator-Precedence Parsing](http://en.wikipedia.org/wiki/Operator-precedence_parser)。此解析技术使用二元运算符的优先级来选择运算顺序。首先,我们需要一个优先级表: 253 | 254 | ```cpp 255 | /// BinopPrecedence - This holds the precedence for each binary operator that is 256 | /// defined. 257 | static std::map BinopPrecedence; 258 | 259 | /// GetTokPrecedence - Get the precedence of the pending binary operator token. 260 | static int GetTokPrecedence() { 261 | if (!isascii(CurTok)) 262 | return -1; 263 | 264 | // Make sure it's a declared binop. 265 | int TokPrec = BinopPrecedence[CurTok]; 266 | if (TokPrec <= 0) return -1; 267 | return TokPrec; 268 | } 269 | 270 | int main() { 271 | // Install standard binary operators. 272 | // 1 is lowest precedence. 273 | BinopPrecedence['<'] = 10; 274 | BinopPrecedence['+'] = 20; 275 | BinopPrecedence['-'] = 20; 276 | BinopPrecedence['*'] = 40; // highest. 277 | ... 278 | } 279 | ``` 280 | 281 | 对于Kaleidoscope的基本形式,我们将仅支持4个元运算符(当然我们可以随意扩展)。以上 `GetTokPrecedence` 函数返回当前token的优先级;如果token不是元运算符,则返回-1。有了映射可以轻松添加新的运算符,并且可以清楚地表明该算法不依赖于所涉及的特定运算符,消除映射并在 `GetTokPrecedence` 函数中进行比较将很容易。(或者只使用固定大小的数组)。 282 | 283 | 有了上面定义的帮助程序,我们现在就可以开始分析二元表达式了。运算符优先级解析的基本思想是将具有潜在歧义的二元运算符的表达式分解为多个部分。考虑例如表达式 ` a + b + (c + d) * e * f + g `。运算符优先级解析将其视为由二元运算符分隔的主表达式流。这样,它将首先解析前导主表达式 `a`,然后将看到对 [+, b] [+, (c + d) ] [*, e] [*, f] 和 [+, g ]。请注意,因为括号是基础表达式,所以二元表达式解析器根本不需要担心像(c + d)这样的嵌套子表达式。 284 | 285 | 首先,一个表达式是一个主表达式,其后可能是[binop,primaryexpr]对的序列: 286 | 287 | ```cpp 288 | /// expression 289 | /// ::= primary binoprhs 290 | /// 291 | static std::unique_ptr ParseExpression() { 292 | auto LHS = ParsePrimary(); 293 | if (!LHS) 294 | return nullptr; 295 | 296 | return ParseBinOpRHS(0, std::move(LHS)); 297 | } 298 | ``` 299 | 300 | `ParseBinOpRHS` 是为我们解析配对序列的函数。它具有一个优先级和一个指向到目前为止已解析的部分的表达式的指针。请注意,`x` 是一个完全有效的表达式:因此,`binoprhs` 允许为空,在这种情况下,它将返回传递给它的表达式。在上面的示例中,代码将 `a` 的表达式传递到其中 `ParseBinOpRHS`,当前标记为 `+`。 301 | 302 | 传递的优先级值`ParseBinOpRHS`指示允许该函数使用的_最小运算符优先级_。例如,如果当前对流为[+,x]并 `ParseBinOpRHS` 以40的优先级传递,则它将不删去任何token(因为“ +”的优先级仅为20)。考虑到这一点,`ParseBinOpRHS`从以下内容开始: 303 | 304 | ```cpp 305 | /// binoprhs 306 | /// ::= ('+' primary)* 307 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 308 | std::unique_ptr LHS) { 309 | // If this is a binop, find its precedence. 310 | while (1) { 311 | int TokPrec = GetTokPrecedence(); 312 | 313 | // If this is a binop that binds at least as tightly as the current binop, 314 | // consume it, otherwise we are done. 315 | if (TokPrec < ExprPrec) 316 | return LHS; 317 | ``` 318 | 319 | 此代码获取当前token的优先级,并与传入的优先级进行比较。因为我们将无效token定义为优先级为-1,它比任何一个运算符的优先级都小,我们可以借助它来获知二元表达式已经结束。若当前的token是运算符,我们继续: 320 | 321 | ```cpp 322 | // Okay, we know this is a binop. 323 | int BinOp = CurTok; 324 | getNextToken(); // eat binop 325 | 326 | // Parse the primary expression after the binary operator. 327 | auto RHS = ParsePrimary(); 328 | if (!RHS) 329 | return nullptr; 330 | ``` 331 | 332 | 这样,此代码将删除(先记住)二元运算符,然后解析随后的主表达式。这样就构成了整个对,对于正在运行的示例,第一对是[+,b]。 333 | 334 | 现在,我们解析了表达式的左侧和一对RHS序列,现在我们必须确定表达式关联的方式。特别是,我们可以设置为 `(a + b) binop unparsed` 或 ` a + (b binop unparsed) ` 。为了确定这一点,我们先看 `binop` 以确定其优先级,并将其与之前binop的优先级(在本例中为 `+`)进行比较: 335 | 336 | ```cpp 337 | // If BinOp binds less tightly with RHS than the operator after RHS, let 338 | // the pending operator take RHS as its LHS. 339 | int NextPrec = GetTokPrecedence(); 340 | if (TokPrec < NextPrec) { 341 | ``` 342 | 343 | 如果 `RHS` 右侧的binop的优先级低于或等于当前运算符的优先级,则我们知道括号关联为 `(a + b) binop…` 。在我们的示例中,当前运算符为 `+`,下一个运算符为 `+`,我们知道它们具有相同的优先级。在这种情况下,我们将为 `a + b` 创建AST节点,然后继续解析: 344 | 345 | ```cpp 346 | ... if body omitted ... 347 | } 348 | 349 | // Merge LHS/RHS. 350 | LHS = std::make_unique(BinOp, std::move(LHS), 351 | std::move(RHS)); 352 | } // loop around to the top of the while loop. 353 | } 354 | ``` 355 | 356 | 在上面的示例中,这会将 `a + b +` 变成 ` (a + b)` 并执行循环的下一个迭代,并以 `+` 作为当前标记。上面的代码记录下来。接下来解析 `(c + d)` 作为基础表达式,这使得当前对等于[+, (c + d) ]。然后,它将使用 `*` 作为主要对象右侧的binop评估上面的 `if` 条件。在这种情况下,`*` 的优先级高于 `+` 的优先级,因此将输入if条件。 357 | 358 | 这里剩下的关键问题是“如果条件如何完全解析右侧”?特别是,要为我们的示例正确构建AST,需要将所有 `(c + d) * e * f` 作为RHS表达变量。做到这一点的代码非常简单(上面两个块中的代码重复了上下文): 359 | 360 | ```cpp 361 | // If BinOp binds less tightly with RHS than the operator after RHS, let 362 | // the pending operator take RHS as its LHS. 363 | int NextPrec = GetTokPrecedence(); 364 | if (TokPrec < NextPrec) { 365 | RHS = ParseBinOpRHS(TokPrec+1, std::move(RHS)); 366 | if (!RHS) 367 | return nullptr; 368 | } 369 | // Merge LHS/RHS. 370 | LHS = std::make_unique(BinOp, std::move(LHS), 371 | std::move(RHS)); 372 | } // loop around to the top of the while loop. 373 | } 374 | ``` 375 | 376 | 至此,我们知道主RHS的二进制运算符的优先级高于我们当前正在解析的binop。因此,我们知道运算符优先级均高于 `+` 的任何对序列都应一起解析,并作为 `RHS` 返回。为此,我们递归调用 `ParseBinOpRHS` 指定 `TokPrec + 1` 的函数作为继续运行所需的最低优先级。在上面的示例中,这将导致它返回 `(c + d) * e * f` 的AST节点作为RHS,然后将其设置为 `+` 表达式的RHS。 377 | 378 | 最后,在while循环的下一次迭代中,`+ g` 段被解析并添加到AST中。有了这段代码(14行平凡的代码),我们就以一种非常优雅的方式正确地处理了完全通用的二进制表达式解析。这是这段代码的精妙之处。我们建议再通过一些复杂的例子来贯穿它,以了解其工作原理。 379 | 380 | 以上结束了对二元表达式的处理。此时,我们可以将解析器指向任意token流,并根据该token流构建一个表达式,并在不属于该表达式的第一个token处停止。接下来,我们需要处理函数定义等。 381 | 382 | ### 解析其他部分 383 | 384 | 接下来缺少的是函数原型的处理。在Kaleidoscope中,这些用于“外部”函数声明以及函数主体定义。执行此操作的代码简单明了,而且不是很有趣(一旦我们保留了表达式,就可以直接处理了): 385 | 386 | ```cpp 387 | /// prototype 388 | /// ::= id '(' id* ')' 389 | static std::unique_ptr ParsePrototype() { 390 | if (CurTok != tok_identifier) 391 | return LogErrorP("Expected function name in prototype"); 392 | 393 | std::string FnName = IdentifierStr; 394 | getNextToken(); 395 | 396 | if (CurTok != '(') 397 | return LogErrorP("Expected '(' in prototype"); 398 | 399 | // Read the list of argument names. 400 | std::vector ArgNames; 401 | while (getNextToken() == tok_identifier) 402 | ArgNames.push_back(IdentifierStr); 403 | if (CurTok != ')') 404 | return LogErrorP("Expected ')' in prototype"); 405 | 406 | // success. 407 | getNextToken(); // eat ')'. 408 | 409 | return std::make_unique(FnName, std::move(ArgNames)); 410 | } 411 | ``` 412 | 413 | 鉴于此,函数定义非常简单,只需一个原型以及一个用于实现主体的表达式即可: 414 | 415 | ```cpp 416 | /// definition ::= 'def' prototype expression 417 | static std::unique_ptr ParseDefinition() { 418 | getNextToken(); // eat def. 419 | auto Proto = ParsePrototype(); 420 | if (!Proto) return nullptr; 421 | 422 | if (auto E = ParseExpression()) 423 | return std::make_unique(std::move(Proto), std::move(E)); 424 | return nullptr; 425 | } 426 | ``` 427 | 428 | 另外,我们支持 'extern' 来声明诸如 'sin' 和 'cos' 之类的函数,并支持用户函数的正向声明。这些 `extern` 只是没有主体的原型: 429 | 430 | ```cpp 431 | /// external ::= 'extern' prototype 432 | static std::unique_ptr ParseExtern() { 433 | getNextToken(); // eat extern. 434 | return ParsePrototype(); 435 | } 436 | ``` 437 | 438 | 最后,我们将让用户输入任意的外层表达式(top-level expressions),在运行的同时会计算出表达式结果。为此,我们需要处理无参数函数: 439 | 440 | ```cpp 441 | /// toplevelexpr ::= expression 442 | static std::unique_ptr ParseTopLevelExpr() { 443 | if (auto E = ParseExpression()) { 444 | // Make an anonymous proto. 445 | auto Proto = std::make_unique("", std::vector()); 446 | return std::make_unique(std::move(Proto), std::move(E)); 447 | } 448 | return nullptr; 449 | } 450 | ``` 451 | 452 | 现在我们已经完成了所有的工作,接下来我们构建一个小驱动程序,它将使我们能够实际执行我们目前为止已构建的代码! 453 | 454 | ### 驱动代码 455 | 456 | 该驱动程序仅通过顶级调度循环调用所有解析块。这里没有什么注意的,所以我只包括顶级循环。请参阅以下[第二章完整代码清单](https://www.tuhaoxin.cn/articles/2019/10/02/1569977094025.html)的“top-level expressions”部分中的完整代码。 457 | 458 | ```cpp 459 | /// top ::= definition | external | expression | ';' 460 | static void MainLoop() { 461 | while (1) { 462 | fprintf(stderr, "ready> "); 463 | switch (CurTok) { 464 | case tok_eof: 465 | return; 466 | case ';': // ignore top-level semicolons. 467 | getNextToken(); 468 | break; 469 | case tok_def: 470 | HandleDefinition(); 471 | break; 472 | case tok_extern: 473 | HandleExtern(); 474 | break; 475 | default: 476 | HandleTopLevelExpression(); 477 | break; 478 | } 479 | } 480 | } 481 | ``` 482 | 483 | 最有趣的部分是我们忽略了顶级分号。你问为什么呢?根本原因是,如果您在命令行中键入 `4 + 5` ,则解析器将不知道这是否是您要键入的内容的结尾。例如,在下一行中,您可以键入 `def foo…` ,在这种情况下4 + 5是顶级表达式的结尾。或者,您可以键入 `* 6` ,这将继续表达式。使用顶级分号可以使您键入` 4 + 5;` ,解析器将知道已完成输入。 484 | 485 | ### 小结 486 | 487 | 通过不到400行的注释代码(240行非注释,非空白代码),我们完全定义了最小语言,包括词法分析器,解析器和AST构建器。完成此操作后,可执行文件将验证Kaleidoscope代码,并告诉我们其语法是否无效。例如,这是一个交互示例: 488 | 489 | ```console 490 | $ ./a.out 491 | ready> def foo(x y) x+foo(y, 4.0); 492 | Parsed a function definition. 493 | ready> def foo(x y) x+y y; 494 | Parsed a function definition. 495 | Parsed a top-level expr 496 | ready> def foo(x y) x+y ); 497 | Parsed a function definition. 498 | Error: unknown token when expecting an expression 499 | ready> extern sin(a); 500 | ready> Parsed an extern 501 | ready> ^D 502 | $ 503 | ``` 504 | 505 | 这里有很多扩展空间。我们可以定义新的AST节点,以多种方式扩展语言,等等。[在下一部分: 第三章生成LLVM中间代码IR](https://www.tuhaoxin.cn/articles/2019/10/02/1569989065380.html)中,我们将描述如何从AST生成LLVM中间表示(IR)。 506 | 507 | ### 完整代码清单 508 | 509 | **特别注意**: 官方给的例子有些问题,运行出错了,请参考以下运行参数和程序进行编译和运行并演示。 510 | 511 | 这是我们运行示例的完整代码清单。因为这使用了LLVM库,所以我们需要将它们链接起来。为此,我们使用[llvm-config](http://llvm.org/cmds/llvm-config.html)工具通知makefile /命令行有关要使用哪些选项的信息: 512 | 513 | ```console 514 | # Compile 515 | clang++ -g -O3 chapter2-Implementing-a-Parser-and-AST.cpp `llvm-config --cxxflags --system-libs --libs` 516 | # Run 517 | ./a.out 518 | ``` 519 | 520 | 以下是本章节代码: 521 | 522 | [chapter2-Implementing-a-Parser-and-AST.cpp 523 | ](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/chapter2-Implementing-a-Parser-and-AST.cpp) 524 | 525 | ---- 526 | 527 | 参考: [Kaleidoscope: Implementing a Parser and AST](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl02.html) 528 | 529 | -------------------------------------------------------------------------------- /blog/3.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第三章:生成LLVM中间代码IR 2 | 3 | 本文是[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)系列第三章,主要实现将AST转化为LLVM IR的功能。 4 | 5 | ### 第三章简介 6 | 7 | 欢迎来到“[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)”教程的第三章。本章介绍如何将第二章中构建的[抽象语法树](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/2.md)转换为LLVM IR。本章将告诉我们一些有关LLVM如何工作的知识,并演示它的易用性。构建词法分析器和解析器要比生成LLVM IR代码多得多。 8 | 9 | **请注意**:本章及更高版本中的代码要求LLVM 3.7或更高版本。LLVM 3.6及更低版本将无法使用。还要注意,我们需要使用与您的LLVM版本匹配的本教程版本:如果你使用的是官方LLVM版本,请使用你的版本随附的文档版本或[llvm.org版本页面](http://llvm.org/releases/)上的[文档版本](http://llvm.org/releases/)。 10 | 11 | ### 中间代码生成配置 12 | 13 | 为了生成LLVM IR,我们希望开始一些简单的配置。首先,我们在每个AST类中定义虚拟代码生成(codegen)方法: 14 | 15 | ```cpp 16 | /// ExprAST - Base class for all expression nodes. 17 | class ExprAST { 18 | public: 19 | virtual ~ExprAST() {} 20 | virtual Value *codegen() = 0; 21 | }; 22 | 23 | /// NumberExprAST - Expression class for numeric literals like "1.0". 24 | class NumberExprAST : public ExprAST { 25 | double Val; 26 | 27 | public: 28 | NumberExprAST(double Val) : Val(Val) {} 29 | virtual Value *codegen(); 30 | }; 31 | ... 32 | ``` 33 | 34 | `codegen()` 方法表示要为该AST节点生成中间代码IR及其依赖的所有东西,并且它们都返回 `LLVM Value` 对象。“Value”是用于表示LLVM中的“[静态单一赋值(SSA)](http://en.wikipedia.org/wiki/Static_single_assignment_form)寄存器”或“ SSA value”的类。SSA值最明显的方面是,它们的值是在相关指令执行时计算的,并且直到(如果有)指令重新执行前,它都不会获得新值。换句话说,没有办法“change” SSA值。有关更多信息,请阅读“[静态单一赋值”](http://en.wikipedia.org/wiki/Static_single_assignment_form)。一旦掌握了这些概念,理解它们就会非常自然。 35 | 36 | 请注意,与其将虚拟方法添加到ExprAST类层次结构中,还可以使用 [访问者模式](http://en.wikipedia.org/wiki/Visitor_pattern)或其他方式对此建模。再次说明,本教程将不讨论良好的软件工程实践:就我们的目的而言,添加虚拟方法最简单。 37 | 38 | 我们要做的第二件事是像用于解析器那样的“ LogError”方法,该方法将用于报告在代码生成过程中发现的错误(例如,使用未声明的参数): 39 | 40 | ```cpp 41 | static LLVMContext TheContext; 42 | static IRBuilder<> Builder(TheContext); 43 | static std::unique_ptr TheModule; 44 | static std::map NamedValues; 45 | 46 | Value *LogErrorV(const char *Str) { 47 | LogError(Str); 48 | return nullptr; 49 | } 50 | ``` 51 | 52 | 静态变量将在代码生成期间使用。`TheContext` 是一个不透明的对象,它拥有很多核心的LLVM数据结构,例如类型表和常量值表。我们不需要详细了解它,我们只需要一个实例即可传递给需要它的API。 53 | 54 | 该 `Builder` 对象是一个帮助程序对象,可轻松生成LLVM指令。[IRBuilder](http://llvm.org/doxygen/IRBuilder_8h-source.html)类模板的实例跟踪要插入指令的当前位置,并具有创建新指令的方法。 55 | 56 | `TheModule` 是包含函数和全局变量的LLVM方法。在许多方面,它是LLVM IR用来包含代码的顶层结构。它将拥有我们生成的所有IR的内存,这就是为什么codegen()方法返回原始 `Value*` 而不是unique_ptr 的原因。 57 | 58 | 该 `NamedValues` 映射跟踪当前范围中定义了哪些值,以及它们的LLVM表示形式是什么(换句话说,它是代码的符号表)。在这种形式的Kaleidoscope中,唯一可以引用的是功能参数。这样,在为函数主体生成代码时,函数参数将位于此映射中。 59 | 60 | 有了这些基础知识之后,我们就可以开始讨论如何为每个表达式生成代码了。请注意,这假设 `Builder` 已将设置为将代码生成的配置。现在,我们假设这已经完成,并且仅使用它来生成IR代码。 61 | 62 | ### 表达式代码生成 63 | 64 | 为表达式节点生成LLVM IR代码非常简单:对于我们所有四个表达式节点,少于45行的注释代码就能搞定。首先,对于数字表达式: 65 | 66 | ```cpp 67 | Value *NumberExprAST::codegen() { 68 | return ConstantFP::get(TheContext, APFloat(Val)); 69 | } 70 | ``` 71 | 72 | 在LLVM IR中,数字常量由 `ConstantFP` 类表示,该类将数字值保存在`APFloat`内部(`APFloat`具有保存任意精度的浮点常量的功能)。这段代码基本上只是创建并返回一个`ConstantFP`。请注意,在LLVM IR中,所有常量都唯一并共享。因此,API使用 `foo :: get (...)` 惯用语代替 `new foo (...)` 或 ` foo :: Create (...)`。 73 | 74 | ```cpp 75 | Value *VariableExprAST::codegen() { 76 | // Look this variable up in the function. 77 | Value *V = NamedValues[Name]; 78 | if (!V) 79 | LogErrorV("Unknown variable name"); 80 | return V; 81 | } 82 | ``` 83 | 84 | 使用LLVM,对变量的引用也非常简单。在Kaleidoscope的简单版本中,我们假定变量已经在某个位置生成并且其值可用。实际上,`NamedValues` 映射中唯一可以包含的值就是函数参数。此代码只是检查以查看指定的名称是否在映射中(如果不在映射中,则引用一个未知变量)并返回其值。在以后的章节中,我们将在符号表中添加对[循环归纳变量](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl5.html#for-loop-expression)和[局部变量的支持](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl7.html#user-defined-local-variables)。 85 | 86 | ```cpp 87 | Value *BinaryExprAST::codegen() { 88 | Value *L = LHS->codegen(); 89 | Value *R = RHS->codegen(); 90 | if (!L || !R) 91 | return nullptr; 92 | 93 | switch (Op) { 94 | case '+': 95 | return Builder.CreateFAdd(L, R, "addtmp"); 96 | case '-': 97 | return Builder.CreateFSub(L, R, "subtmp"); 98 | case '*': 99 | return Builder.CreateFMul(L, R, "multmp"); 100 | case '<': 101 | L = Builder.CreateFCmpULT(L, R, "cmptmp"); 102 | // Convert bool 0/1 to double 0.0 or 1.0 103 | return Builder.CreateUIToFP(L, Type::getDoubleTy(TheContext), 104 | "booltmp"); 105 | default: 106 | return LogErrorV("invalid binary operator"); 107 | } 108 | } 109 | ``` 110 | 111 | 二元运算符开始变得越来越意思。这里的基本思想是我们递归地为表达式的左侧生成代码,然后再为右侧生成代码,然后计算二进制表达式的结果。在此代码中,我们对操作码进行了简单的切换以创建正确的LLVM指令。 112 | 113 | 在上面的示例中,LLVM构建器类开始显示其值。IRBuilder知道在何处插入新创建的指令,我们要做的就是指定要创建的指令(例如,使用`CreateFAdd`),要使用的操作数(`L`以及`R`此处),并可以选择为生成的指令提供名称。 114 | 115 | LLVM的一个好处是名称只是一个提示。例如,如果上面的代码发出多个“ `addtmp` 变量,则LLVM将自动为每个变量提供一个递增的唯一数字后缀。指令的本地值名称纯粹是可选的,但是它使读取IR转储更加容易。 116 | 117 | [LLVM指令](https://llvm.org/docs/tutorial/LangRef.html#instruction-reference)受到严格的规则约束:例如,一条[add指令](https://llvm.org/docs/tutorial/LangRef.html#add-instruction)的Left和Right运算符必须具有相同的类型,并且add的结果类型必须与操作数类型匹配。因为Kaleidoscope中的所有值都是双精度的,所以这使得用于add,sub和mul的代码非常简单。 118 | 119 | 另一方面,LLVM指定[fcmp指令](https://llvm.org/docs/tutorial/LangRef.html#fcmp-instruction)始终返回 `i1` 值(一位整数)。问题在于Kaleidoscope希望该值为0.0或1.0。为了获得这些语义,我们将fcmp指令与[uitofp指令](https://llvm.org/docs/tutorial/LangRef.html#uitofp-to-instruction)结合在一起。该指令通过将输入视为无符号值,将其输入整数转换为浮点值。相反,如果我们使用[sitofp指令](https://llvm.org/docs/tutorial/LangRef.html#sitofp-to-instruction),则Kaleidoscope `<` 运算符将根据输入值返回0.0和-1.0。 120 | 121 | ```cpp 122 | Value *CallExprAST::codegen() { 123 | // Look up the name in the global module table. 124 | Function *CalleeF = TheModule->getFunction(Callee); 125 | if (!CalleeF) 126 | return LogErrorV("Unknown function referenced"); 127 | 128 | // If argument mismatch error. 129 | if (CalleeF->arg_size() != Args.size()) 130 | return LogErrorV("Incorrect # arguments passed"); 131 | 132 | std::vector ArgsV; 133 | for (unsigned i = 0, e = Args.size(); i != e; ++i) { 134 | ArgsV.push_back(Args[i]->codegen()); 135 | if (!ArgsV.back()) 136 | return nullptr; 137 | } 138 | 139 | return Builder.CreateCall(CalleeF, ArgsV, "calltmp"); 140 | } 141 | ``` 142 | 143 | 使用LLVM,函数调用的代码生成非常简单。上面的代码最初在LLVM模块的符号表中进行功能名称查找。回想一下,LLVM模块是包含我们正在JITing的功能的容器。通过为每个函数指定与用户指定的名称相同的名称,我们可以使用LLVM符号表为我们解析函数名称。 144 | 145 | 一旦有了要调用的函数,就可以递归地对要传递的每个参数进行代码生成,并创建LLVM[调用指令](https://llvm.org/docs/tutorial/LangRef.html#call-instruction)。请注意,默认情况下,LLVM使用本机C调用约定,从而使这些调用也可以调用标准库函数(如“ sin”和“ cos”)而无需付出额外的努力。 146 | 147 | 这总结了我们到目前为止在Kaleidoscope中使用的四个基本表达式的处理。我们也可以随意进入并添加更多内容。例如,通过浏览[LLVM语言手册](https://llvm.org/docs/tutorial/LangRef.html)我们会发现其他一些有趣的指令,这些指令确实很容易插入到我们的基本框架中。 148 | 149 | ### 函数代码生成 150 | 151 | 原型和函数的代码生成必须处理许多细节,这使得它们的代码不如表达式代码生成简单,但可以让我们举例说明一些重点。首先,我们首先看原型的代码生成:它们既用于函数体,又用于外部函数声明。该代码以以下内容开头: 152 | 153 | ```cpp 154 | Function *PrototypeAST::codegen() { 155 | // Make the function type: double(double,double) etc. 156 | std::vector Doubles(Args.size(), 157 | Type::getDoubleTy(TheContext)); 158 | FunctionType *FT = 159 | FunctionType::get(Type::getDoubleTy(TheContext), Doubles, false); 160 | 161 | Function *F = 162 | Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get()); 163 | ``` 164 | 165 | 此代码将大量功能打包成几行。首先请注意,此函数返回 `Function *` 而不是` Value *` 。因为“prototype”实际上是在谈论函数的外部接口(而不是表达式计算的值),所以有意义的是,它返回的是代码生成时对应的LLVM函数。 166 | 167 | 调用 `FunctionType::get`create`FunctionType` 应该用于给定的原型。由于Kaleidoscope中的所有函数参数均为double类型,因此第一行将创建一个 `N` 个LLVM double类型的向量。然后,它使用该`Functiontype::get`方法创建一个函数类型,该函数类型将 `N` 个双精度值作为参数,并返回一个双精度值,而不是vararg(假参数表明了这一点)。请注意,LLVM中的类型就像常量一样是唯一的,因此我们不必“新建”一个类型,而是直接“获取”它。 168 | 169 | 上面的最后一行实际上创建了与原型相对应的IR函数。这表明要使用的类型,链接和名称,以及要插入的模块。“[外部链接](https://llvm.org/docs/tutorial/LangRef.html#linkage)”是指该功能可以在当前模块外部定义和/或可以由模块外部的函数调用。传入的名称是用户指定的名称:由于指定了“`TheModule`”,因此该名称已注册在“`TheModule`”的符号表中。 170 | 171 | ```cpp 172 | // Set names for all arguments. 173 | unsigned Idx = 0; 174 | for (auto &Arg : F->args()) 175 | Arg.setName(Args[Idx++]); 176 | 177 | return F; 178 | ``` 179 | 180 | 最后,我们根据Prototype中提供的名称设置函数的每个参数的名称。此步骤不是绝对必要的,但是保持名称的一致性可使IR更具可读性,并允许后续代码直接引用其名称的参数,而不必在Prototype AST中进行查找。 181 | 182 | 至此,我们有了一个没有主体的函数原型。这就是LLVM IR表示函数声明的方式。对于万花筒中的外部陈述,这是我们需要做的。但是对于函数定义,我们需要代码生成并附加一个函数体。 183 | 184 | ```cpp 185 | Function *FunctionAST::codegen() { 186 | // First, check for an existing function from a previous 'extern' declaration. 187 | Function *TheFunction = TheModule->getFunction(Proto->getName()); 188 | 189 | if (!TheFunction) 190 | TheFunction = Proto->codegen(); 191 | 192 | if (!TheFunction) 193 | return nullptr; 194 | 195 | if (!TheFunction->empty()) 196 | return (Function*)LogErrorV("Function cannot be redefined."); 197 | ``` 198 | 199 | 对于函数定义,我们首先在TheModule的符号表中搜索该函数的现有版本(如果已经使用 `extern` 语句创建了该版本)。如果 `Module :: getFunction` 返回`null` ,则不存在以前的版本,因此我们将从Prototype中返回一个。无论哪种情况,我们都想在开始之前断言该函数为空(即没有主体)。 200 | 201 | ```cpp 202 | // Create a new basic block to start insertion into. 203 | BasicBlock *BB = BasicBlock::Create(TheContext, "entry", TheFunction); 204 | Builder.SetInsertPoint(BB); 205 | 206 | // Record the function arguments in the NamedValues map. 207 | NamedValues.clear(); 208 | for (auto &Arg : TheFunction->args()) 209 | NamedValues[Arg.getName()] = &Arg; 210 | ``` 211 | 212 | 现在,我们开始进行 `Builder` 设置。第一行创建一个新的[basic block](http://en.wikipedia.org/wiki/Basic_block)(名为 `entry` ),将其插入`TheFunction` 。然后第二行告诉 `Builder`,新指令应插入到新基本块的末尾。LLVM中的基本块是定义[Control Flow Graph](http://en.wikipedia.org/wiki/Control_flow_graph)的功能的重要组成部分。由于我们没有任何控制流,因此我们的函数此时仅包含一个块。我们将在[第五章中](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl05.html)解决此问题:)。 213 | 214 | 接下来,我们将函数参数添加到 `NamedValues` 映射中(首先将其清除后),以便 `VariableExprAST` 节点可以访问它们。 215 | 216 | ```cpp 217 | if (Value *RetVal = Body->codegen()) { 218 | // Finish off the function. 219 | Builder.CreateRet(RetVal); 220 | 221 | // Validate the generated code, checking for consistency. 222 | verifyFunction(*TheFunction); 223 | 224 | return TheFunction; 225 | } 226 | ``` 227 | 228 | 设置插入点并填充 `NamedValues` 映射后,我们将调用该 `codegen()` 方法作为函数的根表达式。如果没有错误发生,它将发出代码以将表达式计算到输入块中,并返回计算出的值。假设没有错误,我们然后创建LLVM[ret指令](https://llvm.org/docs/tutorial/LangRef.html#ret-instruction),以完成该功能。构建函数后,我们将调用LLVM提供的 `verifyFunction`,该函数对生成的代码进行各种一致性检查,以确定我们的编译器是否在正确执行所有操作。使用它很重要:它可以捕获很多错误。函数完成并验证后,我们将其返回。 229 | 230 | ```cpp 231 | // Error reading body, remove function. 232 | TheFunction->eraseFromParent(); 233 | return nullptr; 234 | } 235 | ``` 236 | 237 | 这里剩下的唯一内容是错误情况的处理。为简单起见,我们仅通过删除使用该`eraseFromParent` 方法生成的函数来处理此问题。这使用户可以重新定义以前错误输入的函数:如果我们不删除它,该函数将与主体一起存在于符号表中,以防止将来重新定义。 238 | 239 | 但是,此代码确实存在一个漏洞:如果该 `FunctionAST::codegen()` 方法找到了现有的IR函数,则不会根据定义自己的原型来验证其签名。这意味着较早的“外部”声明将优先于函数定义的签名,这可能导致代码生成失败,例如,如果函数参数的命名不同。有多种方法可以修复此错误,请看你能想到些什么!以下是一个测试用例: 240 | 241 | ``` 242 | extern foo(a); # ok, defines foo. 243 | def foo(b) b; # Error: Unknown variable name. (decl using 'a' takes precedence). 244 | ``` 245 | 246 | ### 驱动代码及思路总结 247 | 248 | 就目前而言,LLVM的代码生成并不能真正为我们带来很多好处,只是我们可以查看漂亮的IR调用。示例代码将对代码生成的调用插入 `HandleDefinition`,`HandleExtern` 等函数中,然后转储LLVM IR。这为查看LLVM IR的简单功能提供了一种好方法。例如: 249 | 250 | ```llvm 251 | ready> 4+5; 252 | Read top-level expression: 253 | define double @0() { 254 | entry: 255 | ret double 9.000000e+00 256 | } 257 | ``` 258 | 259 | 请注意解析器如何将顶级表达式转换为我们的匿名函数。在下一章中添加[JIT支持](https://www.tuhaoxin.cn/articles/2019/10/02/1570001336572.html#添加JIT编译器)时,这将非常方便。还要注意,该代码是按字面意思转录的,除了IRBuilder进行的简单常量折叠外,没有执行任何优化。我们将在下一章中显式[添加优化](https://www.tuhaoxin.cn/articles/2019/10/02/1570001336572.html#简单常量折叠)。 260 | 261 | ```llvm 262 | ready> def foo(a b) a*a + 2*a*b + b*b; 263 | Read function definition: 264 | define double @foo(double %a, double %b) { 265 | entry: 266 | %multmp = fmul double %a, %a 267 | %multmp1 = fmul double 2.000000e+00, %a 268 | %multmp2 = fmul double %multmp1, %b 269 | %addtmp = fadd double %multmp, %multmp2 270 | %multmp3 = fmul double %b, %b 271 | %addtmp4 = fadd double %addtmp, %multmp3 272 | ret double %addtmp4 273 | } 274 | ``` 275 | 276 | 这显示了一些简单的算法。请注意,它与我们用来创建指令的LLVM构建器调用非常相似。 277 | 278 | ```llvm 279 | ready> def bar(a) foo(a, 4.0) + bar(31337); 280 | Read function definition: 281 | define double @bar(double %a) { 282 | entry: 283 | %calltmp = call double @foo(double %a, double 4.000000e+00) 284 | %calltmp1 = call double @bar(double 3.133700e+04) 285 | %addtmp = fadd double %calltmp, %calltmp1 286 | ret double %addtmp 287 | } 288 | ``` 289 | 290 | 这显示了一些函数调用。请注意,如果调用此函数,将花费很长时间执行。将来,我们将添加条件控制流,以使递归真正有用。 291 | 292 | ```llvm 293 | ready> extern cos(x); 294 | Read extern: 295 | declare double @cos(double) 296 | 297 | ready> cos(1.234); 298 | Read top-level expression: 299 | define double @1() { 300 | entry: 301 | %calltmp = call double @cos(double 1.234000e+00) 302 | ret double %calltmp 303 | } 304 | 305 | ``` 306 | 307 | 这显示了libm“ cos”函数的外部,以及对其的调用。 308 | 309 | ```llvm 310 | ready> ^D 311 | ; ModuleID = 'my cool jit' 312 | 313 | define double @0() { 314 | entry: 315 | %addtmp = fadd double 4.000000e+00, 5.000000e+00 316 | ret double %addtmp 317 | } 318 | 319 | define double @foo(double %a, double %b) { 320 | entry: 321 | %multmp = fmul double %a, %a 322 | %multmp1 = fmul double 2.000000e+00, %a 323 | %multmp2 = fmul double %multmp1, %b 324 | %addtmp = fadd double %multmp, %multmp2 325 | %multmp3 = fmul double %b, %b 326 | %addtmp4 = fadd double %addtmp, %multmp3 327 | ret double %addtmp4 328 | } 329 | 330 | define double @bar(double %a) { 331 | entry: 332 | %calltmp = call double @foo(double %a, double 4.000000e+00) 333 | %calltmp1 = call double @bar(double 3.133700e+04) 334 | %addtmp = fadd double %calltmp, %calltmp1 335 | ret double %addtmp 336 | } 337 | 338 | declare double @cos(double) 339 | 340 | define double @1() { 341 | entry: 342 | %calltmp = call double @cos(double 1.234000e+00) 343 | ret double %calltmp 344 | } 345 | ``` 346 | 347 | 退出当前演示时(通过在Linux上通过CTRL + D或在Windows上通过CTRL + Z和ENTER发送EOF),它将退出生成的整个模块的IR。在这里,我们可以看到具有相互参照的所有功能的全景图。 348 | 349 | Kaleidoscope教程的第三章到此完成。接下来,我们将描述如何为此[[添加JIT和Optimizer支持](https://www.tuhaoxin.cn/articles/2019/10/02/1570001336572.html)],以便我们实际上可以开始运行代码! 350 | 351 | ### 完整代码清单 352 | 353 | 这是我们正在运行的示例的完整代码清单,并通过LLVM代码生成器进行了增强处理。因为这使用了LLVM库,所以我们需要将它们链接起来。为此,我们使用[llvm-config](http://llvm.org/cmds/llvm-config.html)工具通知makefile /命令行有关要使用哪些选项的信息: 354 | 355 | ```console 356 | # Compile 357 | clang++ -g -O3 chapter3-Code-generation-to-LLVM-IR.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core` -o toy 358 | # Run 359 | ./toy 360 | 361 | ``` 362 | 363 | 以下是代码清单: 364 | 365 | [chapter3-Code-generation-to-LLVM-IR.cpp](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/chapter3-Code-generation-to-LLVM-IR.cpp) 366 | 367 | ---- 368 | 参考: [Kaleidoscope: Code generation to LLVM IR](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl03.html) 369 | 370 | -------------------------------------------------------------------------------- /blog/4.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第四章:添加JIT和Optimizer支持 2 | 3 | 本文是[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)系列第四章,主要添加JIT编译器及LLVM中部分优化功能。 4 | 5 | ### 第四章简介 6 | 7 | 欢迎来到“[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)”教程的第四章。前一至三章介绍了一种简单语言的实现并增加了对生成LLVM IR的支持。本章介绍了两种新技术:为我们的语言添加优化器支持,以及添加JIT编译器支持。这些补充内容将演示如何为Kaleidoscope语言获得漂亮,高效的代码。 8 | 9 | ### 简单常数折叠 10 | 11 | 我们在第三章中的演示非常优雅并且易于扩展,不幸的是,它不会产生出色的代码。但是,IRBuilder在编译简单代码时确实为我们提供了明显的优化,例如: 12 | 13 | ```llvm 14 | ready> def test(x) 1+2+x; 15 | Read function definition: 16 | define double @test(double %x) { 17 | entry: 18 | %addtmp = fadd double 3.000000e+00, %x 19 | ret double %addtmp 20 | } 21 | ``` 22 | 23 | 此代码不是通过解析输入而构建的AST的形式。那将是: 24 | 25 | ```llvm 26 | ready> def test(x) 1+2+x; 27 | Read function definition: 28 | define double @test(double %x) { 29 | entry: 30 | %addtmp = fadd double 2.000000e+00, 1.000000e+00 31 | %addtmp1 = fadd double %addtmp, %x 32 | ret double %addtmp1 33 | } 34 | ``` 35 | 36 | 如上所示,常量折叠是非常常见且非常重要的优化:如此之多,以至于许多语言实现者在其AST表示中实现了常量折叠支持。 37 | 38 | 使用LLVM,我们在AST中不需要此支持。由于所有构建LLVM IR的调用都经过LLVM IR构建器,因此构建器本身会检查调用时是否存在持续的折叠机会。如果是这样,它只执行常量折叠并返回常量,而不创建指令。 39 | 40 | 实际上这很容易,我们建议 `IRBuilder` 在生成此类代码时始终使用。它没有使用“语法上的开销”(我们不必在各处进行常量检查编译器),并且在某些情况下(特别是对于具有宏预处理器或宏的语言而言)可以显着减少LLVM IR的数量。避免使用很多常量。 41 | 42 | 另一方面,`IRBuilder`它受以下事实的限制:它在生成代码时会与代码内联地进行所有分析。如果我们使用一个稍微复杂一点的示例: 43 | 44 | ```llvm 45 | ready> def test(x) (1+2+x)*(x+(1+2)); 46 | ready> Read function definition: 47 | define double @test(double %x) { 48 | entry: 49 | %addtmp = fadd double 3.000000e+00, %x 50 | %addtmp1 = fadd double %x, 3.000000e+00 51 | %multmp = fmul double %addtmp, %addtmp1 52 | ret double %multmp 53 | } 54 | ``` 55 | 56 | 在这种情况下,乘法的LHS和RHS是相同的值。我们希望看到它生成`tmp=x+3;result=tmp*tmp;`,而不是计算两次 `x+3`。 57 | 58 | 不幸的是,没有任何本地分析方法能够检测和纠正此问题。这通常需要两种转换:表达式的重新关联(以使添加在词法上相同)和通用子表达式消除(CSE)以删除冗余添加指令。幸运的是,LLVM以“Passes”的形式提供了您可以使用的多种优化。 59 | 60 | ### LLVM优化Passes 61 | 62 | **警告**: 由于过渡到新的PassManager基础结构`llvm::legacy::FunctionPassManager`,因此可以在[LegacyPassManager.h中](http://llvm.org/doxygen/classllvm_1_1legacy_1_1FunctionPassManager.html)找到基于本教程的[内容](http://llvm.org/doxygen/classllvm_1_1legacy_1_1FunctionPassManager.html)。出于本教程的目的,我们应在传递管理器转换完成之前使用以上内容。 63 | 64 | LLVM提供了许多优化passes,这些passes可以完成许多不同的事情并具有不同的权衡。与其他系统不同,LLVM不会错误地认为一组优化适用于所有语言和所有情况。LLVM允许编译器实施者对要使用的优化,以何种顺序以及在哪种情况下做出完整的决策。 65 | 66 | 作为一个具体示例,LLVM支持两个“整个模块”遍历,它们遍历了尽可能多的代码体(通常是整个文件,但是如果在链接时运行,则这可能是整个程序的重要部分) 。它还支持并包括“pre-function”遍历,这些遍历仅一次在一项功能上运行,而无需查看其他功能。有关通行证及其运行方式的更多信息,请参见 [How to Write a Pass] (https://llvm.org/docs/tutorial/WritingAnLLVMPass.html)文档和 [List of LLVM Passes] (https://llvm.org/docs/tutorial/Passes.html)。 67 | 68 | 对于Kaleidoscope,我们目前正在动态生成函数代码,每次用户输入时一次生成一个函数代码。我们并没有在这种设置中寻求最终的优化体验,但是我们也想在其中找到简单快捷的功能可能。这样,我们将在用户键入函数时选择运行一些针对每个函数的优化。如果我们要创建“静态Kaleidoscope编译器”,我们将完全使用现在的代码,只是推迟运行优化过程,直到解析了整个文件。 69 | 70 | 为了使每个功能都可以进行优化,我们需要设置一个[FunctionPassManager](https://llvm.org/docs/tutorial/WritingAnLLVMPass.html#what-passmanager-doesr)来保存和组织要运行的LLVM优化。一旦有了这些,就可以添加一组优化来运行。对于每个要优化的模块,我们都需要一个新的FunctionPassManager,因此我们将编写一个函数来创建和初始化模块,以及为我们设置过程管理器: 71 | 72 | ```cpp 73 | void InitializeModuleAndPassManager(void) { 74 | // Open a new module. 75 | TheModule = std::make_unique("my cool jit", TheContext); 76 | 77 | // Create a new pass manager attached to it. 78 | TheFPM = std::make_unique(TheModule.get()); 79 | 80 | // Do simple "peephole" optimizations and bit-twiddling optzns. 81 | TheFPM->add(createInstructionCombiningPass()); 82 | // Reassociate expressions. 83 | TheFPM->add(createReassociatePass()); 84 | // Eliminate Common SubExpressions. 85 | TheFPM->add(createGVNPass()); 86 | // Simplify the control flow graph (deleting unreachable blocks, etc). 87 | TheFPM->add(createCFGSimplificationPass()); 88 | 89 | TheFPM->doInitialization(); 90 | } 91 | ``` 92 | 93 | 该代码初始化全局模块 `TheModule` 和 `TheFPM` 附加到函数传递管理器 `TheModule` 。设置通道管理器后,我们将使用一系列“添加”调用来添加一堆LLVM通道。 94 | 95 | 在这种情况下,我们选择添加四个优化passes。我们在这里选择的pass是一组相当标准的“cleanup”优化,可用于各种代码。我不会深入研究他们的工作,但是,相信我,他们是一个很好的起点。 96 | 97 | 设置PassManager后,我们需要使用它。我们通过在构造新创建的函数之后(在中`FunctionAST::codegen()`)但在将其返回给客户端之前运行它来执行此操作: 98 | 99 | ```cpp 100 | if (Value *RetVal = Body->codegen()) { 101 | // Finish off the function. 102 | Builder.CreateRet(RetVal); 103 | 104 | // Validate the generated code, checking for consistency. 105 | verifyFunction(*TheFunction); 106 | 107 | // Optimize the function. 108 | TheFPM->run(*TheFunction); 109 | 110 | return TheFunction; 111 | } 112 | ``` 113 | 114 | 如以上所见,这非常简单。通过 `FunctionPassManager` 优化和更新,以代替`LLVM Function*` ,提高了(希望)它的主体性能。有了这个,我们可以再次尝试上面的测试: 115 | 116 | ```llvm 117 | ready> def test(x) (1+2+x)*(x+(1+2)); 118 | ready> Read function definition: 119 | define double @test(double %x) { 120 | entry: 121 | %addtmp = fadd double %x, 3.000000e+00 122 | %multmp = fmul double %addtmp, %addtmp 123 | ret double %multmp 124 | } 125 | ``` 126 | 127 | 正如预期的那样,我们现在获得了经过优化的代码,从该函数的每次执行中保存了浮点加法指令。 128 | 129 | LLVM提供了多种可在某些情况下使用的优化。有一些[documentation about the various passes](https://llvm.org/docs/tutorial/Passes.html)的文档,但不是很完整。另一个好的想法来源可以来自查看`Clang`开始运行的过程。 `opt` 工具可让我们从命令行尝试使用passes,因此可以查看它们是否有任何作用。 130 | 131 | 现在我们的前端已经有了合理的代码,接下来看我们如何执行它! 132 | 133 | ### 添加JIT编译 134 | 135 | LLVM IR中可用的代码可以应用在多种工具上。例如,我们可以对其进行优化(如上小节所述),可以文本或二进制形式转储,可以将代码编译为某个目标的汇编文件(.s),也可以JIT对其进行编译。LLVM IR表示的好处在于,它是编译器许多不同部分之间的“通用货币”。 136 | 137 | 在本节中,我们将为解释器添加JIT编译器支持。我们希望Kaleidoscope的基本思想是让用户像现在一样输入函数体,但是立即求值他们键入的顶级表达式。例如,如果他们输入“ 1 + 2;”,我们应该求值并打印出3。如果他们定义了一个函数,他们应该能够从命令行中调用它。 138 | 139 | 为此,我们首先准备环境以为当前本机目标创建代码,然后声明并初始化JIT。这是通过调用一些 `InitializeNativeTarget\*` 函数并添加一个全局变量 `TheJIT` 并在 `main` 以下位置将其初始化来完成的: 140 | 141 | ```cpp 142 | static std::unique_ptr TheJIT; 143 | ... 144 | int main() { 145 | InitializeNativeTarget(); 146 | InitializeNativeTargetAsmPrinter(); 147 | InitializeNativeTargetAsmParser(); 148 | 149 | // Install standard binary operators. 150 | // 1 is lowest precedence. 151 | BinopPrecedence['<'] = 10; 152 | BinopPrecedence['+'] = 20; 153 | BinopPrecedence['-'] = 20; 154 | BinopPrecedence['*'] = 40; // highest. 155 | 156 | // Prime the first token. 157 | fprintf(stderr, "ready> "); 158 | getNextToken(); 159 | 160 | TheJIT = std::make_unique(); 161 | 162 | // Run the main "interpreter loop" now. 163 | MainLoop(); 164 | 165 | return 0; 166 | } 167 | ``` 168 | 169 | 我们还需要为JIT设置数据布局: 170 | 171 | ```cpp 172 | void InitializeModuleAndPassManager(void) { 173 | // Open a new module. 174 | TheModule = std::make_unique("my cool jit", TheContext); 175 | TheModule->setDataLayout(TheJIT->getTargetMachine().createDataLayout()); 176 | 177 | // Create a new pass manager attached to it. 178 | TheFPM = std::make_unique(TheModule.get()); 179 | ... 180 | ``` 181 | 182 | KaleidoscopeJIT类是专门为这些教程构建的简单JIT,可在 `llvm-src/examples/Kaleidoscope/include/KaleidoscopeJIT.h` 的LLVM源代码中找到。在后面的章节中,我们将研究它的工作原理并使用新功能对其进行扩展,但是现在,我们将按照给出的内容进行操作。它的API非常简单:`addModule` 在JIT中添加LLVM IR模块,使其功能可以执行;`removeModule` 卸下一个模块,释放与该模块中的代码关联的所有内存; `findSymbol` 允许我们查找指向已编译代码的指针。 183 | 184 | 我们可以使用这个简单的API并更改解析顶级表达式的代码,如下所示: 185 | 186 | ```cpp 187 | static void HandleTopLevelExpression() { 188 | // Evaluate a top-level expression into an anonymous function. 189 | if (auto FnAST = ParseTopLevelExpr()) { 190 | if (FnAST->codegen()) { 191 | 192 | // JIT the module containing the anonymous expression, keeping a handle so 193 | // we can free it later. 194 | auto H = TheJIT->addModule(std::move(TheModule)); 195 | InitializeModuleAndPassManager(); 196 | 197 | // Search the JIT for the __anon_expr symbol. 198 | auto ExprSymbol = TheJIT->findSymbol("__anon_expr"); 199 | assert(ExprSymbol && "Function not found"); 200 | 201 | // Get the symbol's address and cast it to the right type (takes no 202 | // arguments, returns a double) so we can call it as a native function. 203 | double (*FP)() = (double (*)())(intptr_t)ExprSymbol.getAddress(); 204 | fprintf(stderr, "Evaluated to %f\n", FP()); 205 | 206 | // Delete the anonymous expression module from the JIT. 207 | TheJIT->removeModule(H); 208 | } 209 | ``` 210 | 211 | 如果解析和代码生成成功,则下一步是将包含顶级表达式的模块添加到JIT。我们通过调用 `addModule` 来做到这一点,后者会触发模块中所有功能的代码生成,并返回一个句柄,该句柄可用于稍后从JIT中删除该模块。将模块添加到JIT后,就无法再对其进行修改,因此我们还通过调用来打开一个新模块来保存后续代码 `InitializeModuleAndPassManager()`。 212 | 213 | 将模块添加到JIT之后,我们需要获取指向最终生成的代码的指针。为此,我们调用JIT的 `findSymbol` 方法,并传递顶级表达式函数的名称:`__anon_expr`。由于我们只是添加了此函数,因此我们断言 `findSymbol` 返回了一个结果。 214 | 215 | 接下来,我们 `__anon_expr` 通过调用 `getAddress()` 符号获得函数的内存地址。回想一下,我们将顶级表达式编译成一个自包含的LLVM函数,该函数不带任何参数并返回计算出的double。因为LLVM JIT编译器与本机平台ABI相匹配,所以这意味着您可以将结果指针转换为该类型的函数指针并直接调用它。这意味着,JIT编译代码和静态链接到您的应用程序的本机代码之间没有区别。 216 | 217 | 最后,由于我们不支持对顶级表达式进行重新求值,因此在完成释放关联内存的操作后,我们将从JIT中删除该模块。但是请回想一下,我们之前(通过`InitializeModuleAndPassManager`)创建的模块仍处于打开状态,正在等待添加新代码。 218 | 219 | 仅通过这两个更改,让我们看看Kaleidoscope现在是如何工作的! 220 | 221 | ```llvm 222 | ready> 4+5; 223 | Read top-level expression: 224 | define double @0() { 225 | entry: 226 | ret double 9.000000e+00 227 | } 228 | 229 | Evaluated to 9.000000 230 | 231 | ``` 232 | 233 | 以上输出看起来似乎基本正常。该函数的转储显示了“no argument function that always returns double”,我们为键入的每个顶级表达式合成了该函数。这演示了非常基本的功能,但是我们可以做更多的事情吗? 234 | 235 | ```llvm 236 | ready> def testfunc(x y) x + y*2; 237 | Read function definition: 238 | define double @testfunc(double %x, double %y) { 239 | entry: 240 | %multmp = fmul double %y, 2.000000e+00 241 | %addtmp = fadd double %multmp, %x 242 | ret double %addtmp 243 | } 244 | 245 | ready> testfunc(4, 10); 246 | Read top-level expression: 247 | define double @1() { 248 | entry: 249 | %calltmp = call double @testfunc(double 4.000000e+00, double 1.000000e+01) 250 | ret double %calltmp 251 | } 252 | 253 | Evaluated to 24.000000 254 | 255 | ready> testfunc(5, 10); 256 | ready> LLVM ERROR: Program used external function 'testfunc' which could not be resolved! 257 | ``` 258 | 259 | 函数定义和调用也可以,但是最后一行出了点问题。该调用看起来有效,那么发生了什么?你可能已经从API中猜到了,模块是JIT的分配单位,而testfunc是包含匿名表达式的同一模块的一部分。当我们从JIT中删除该模块以释放匿名表达式的内存时,我们删除了`testfunc`它的定义。然后,当我们尝试第二次调用testfunc时,JIT无法找到它。 260 | 261 | 解决此问题的最简单方法是将匿名表达式与其余函数定义放在单独的模块中。只要被调用的每个函数都有一个原型,并且在调用之前将其添加到JIT中,JIT就会愉快地解决跨模块边界的函数调用。通过将匿名表达式放在其他模块中,我们可以删除它而不会影响其余功能。 262 | 263 | 实际上,我们将更进一步,将每个函数放入其自己的模块中。这样做使我们能够利用KaleidoscopeJIT的有用属性,这将使我们的环境更像REPL:函数可以多次添加到JIT(与每个模块都必须具有唯一定义的模块不同)。在KaleidoscopeJIT中查找符号时,它将始终返回最新的定义: 264 | 265 | ```llvm 266 | ready> def foo(x) x + 1; 267 | Read function definition: 268 | define double @foo(double %x) { 269 | entry: 270 | %addtmp = fadd double %x, 1.000000e+00 271 | ret double %addtmp 272 | } 273 | 274 | ready> foo(2); 275 | Evaluated to 3.000000 276 | 277 | ready> def foo(x) x + 2; 278 | define double @foo(double %x) { 279 | entry: 280 | %addtmp = fadd double %x, 2.000000e+00 281 | ret double %addtmp 282 | } 283 | 284 | ready> foo(2); 285 | Evaluated to 4.000000 286 | ``` 287 | 288 | 为了使每个函数都可以驻留在自己的模块中,我们需要一种方法来将先前的函数声明重新生成到我们打开的每个新模块中: 289 | 290 | ```cpp 291 | static std::unique_ptr TheJIT; 292 | 293 | ... 294 | 295 | Function *getFunction(std::string Name) { 296 | // First, see if the function has already been added to the current module. 297 | if (auto *F = TheModule->getFunction(Name)) 298 | return F; 299 | 300 | // If not, check whether we can codegen the declaration from some existing 301 | // prototype. 302 | auto FI = FunctionProtos.find(Name); 303 | if (FI != FunctionProtos.end()) 304 | return FI->second->codegen(); 305 | 306 | // If no existing prototype exists, return null. 307 | return nullptr; 308 | } 309 | 310 | ... 311 | 312 | Value *CallExprAST::codegen() { 313 | // Look up the name in the global module table. 314 | Function *CalleeF = getFunction(Callee); 315 | 316 | ... 317 | 318 | Function *FunctionAST::codegen() { 319 | // Transfer ownership of the prototype to the FunctionProtos map, but keep a 320 | // reference to it for use below. 321 | auto &P = *Proto; 322 | FunctionProtos[Proto->getName()] = std::move(Proto); 323 | Function *TheFunction = getFunction(P.getName()); 324 | if (!TheFunction) 325 | return nullptr; 326 | ``` 327 | 328 | 为此,我们将首先添加一个新的全局 `FunctionProtos`,其中包含每个函数的最新原型。我们还将添加一种便捷方法 `getFunction()`,以替换对的调用 `TheModule->getFunction()`。我们的便捷方法搜索 `TheModule` 现有的函数声明,如果找不到,则回退到从FunctionProtos生成新的声明。在这里,`CallExprAST::codegen()` 我们只需要替换对的调用即可 `TheModule->getFunction()`。在这种情况下,`FunctionAST::codegen()`我们需要先更新FunctionProtos映射,然后调用 `getFunction()`。完成此操作后,我们始终可以在当前模块中为任何先前声明的函数获取函数声明。 329 | 330 | 另外,我们还需要更新HandleDefinition和HandleExtern: 331 | 332 | ```cpp 333 | static void HandleDefinition() { 334 | if (auto FnAST = ParseDefinition()) { 335 | if (auto *FnIR = FnAST->codegen()) { 336 | fprintf(stderr, "Read function definition:"); 337 | FnIR->print(errs()); 338 | fprintf(stderr, "\n"); 339 | TheJIT->addModule(std::move(TheModule)); 340 | InitializeModuleAndPassManager(); 341 | } 342 | } else { 343 | // Skip token for error recovery. 344 | getNextToken(); 345 | } 346 | } 347 | 348 | static void HandleExtern() { 349 | if (auto ProtoAST = ParseExtern()) { 350 | if (auto *FnIR = ProtoAST->codegen()) { 351 | fprintf(stderr, "Read extern: "); 352 | FnIR->print(errs()); 353 | fprintf(stderr, "\n"); 354 | FunctionProtos[ProtoAST->getName()] = std::move(ProtoAST); 355 | } 356 | } else { 357 | // Skip token for error recovery. 358 | getNextToken(); 359 | } 360 | } 361 | ``` 362 | 363 | 在HandleDefinition中,我们添加了两行以将新定义的函数传输到JIT并打开一个新模块。在HandleExtern中,我们只需要添加一行即可将原型添加到FunctionProtos。 364 | 365 | 完成这些更改后,让我们再次尝试REPL(这次删除了匿名函数的转储,现在应该已经知道了: 366 | 367 | ```llvm 368 | ready> def foo(x) x + 1; 369 | ready> foo(2); 370 | Evaluated to 3.000000 371 | 372 | ready> def foo(x) x + 2; 373 | ready> foo(2); 374 | Evaluated to 4.000000 375 | ``` 376 | 377 | 成功! 378 | 379 | 即使使用此简单的代码,我们也获得了一些令人惊讶的强大功能-请查看以下内容: 380 | 381 | ```llvm 382 | ready> extern sin(x); 383 | Read extern: 384 | declare double @sin(double) 385 | 386 | ready> extern cos(x); 387 | Read extern: 388 | declare double @cos(double) 389 | 390 | ready> sin(1.0); 391 | Read top-level expression: 392 | define double @2() { 393 | entry: 394 | ret double 0x3FEAED548F090CEE 395 | } 396 | 397 | Evaluated to 0.841471 398 | 399 | ready> def foo(x) sin(x)*sin(x) + cos(x)*cos(x); 400 | Read function definition: 401 | define double @foo(double %x) { 402 | entry: 403 | %calltmp = call double @sin(double %x) 404 | %multmp = fmul double %calltmp, %calltmp 405 | %calltmp2 = call double @cos(double %x) 406 | %multmp4 = fmul double %calltmp2, %calltmp2 407 | %addtmp = fadd double %multmp, %multmp4 408 | ret double %addtmp 409 | } 410 | 411 | ready> foo(4.0); 412 | Read top-level expression: 413 | define double @3() { 414 | entry: 415 | %calltmp = call double @foo(double 4.000000e+00) 416 | ret double %calltmp 417 | } 418 | 419 | Evaluated to 1.000000 420 | ``` 421 | 422 | JIT是如何知道sin和cos函数的?答案非常简单:KaleidoscopeJIT具有简单的符号解析规则,可用于查找任何给定模块中不可用的符号:首先,它搜索已添加到JIT的所有模块,从最新版本到最旧的,以找到最新的定义。如果在JIT中未找到定义,则它会退回到 `dlsym("sin")` 在Kaleidoscope进程本身上调用。由于 `sin` 是在JIT的地址空间中定义的,因此它仅修补模块中的调用以调用libm版本的 `sin`直。但是在某些情况下,这甚至会更进一步:由于sin和cos是标准数学函数的名称,因此,使用 `sin(1.0)` 上面的“”中的常量调用常量文​​件夹时,它将直接将函数调用运行为正确的结果。 423 | 424 | 将来,我们将看到如何调整此符号解析规则来启用各种有用的功能,从安全性(限制可用于JIT代码的符号集)到基于符号名称的动态代码生成,以及甚至懒惰的编译。 425 | 426 | 符号解析规则的直接好处是我们现在可以通过编写任意C ++代码来实现操作来扩展语言。例如,如果我们添加: 427 | 428 | ```cpp 429 | #ifdef _WIN32 430 | #define DLLEXPORT __declspec(dllexport) 431 | #else 432 | #define DLLEXPORT 433 | #endif 434 | 435 | /// putchard - putchar that takes a double and returns 0. 436 | extern "C" DLLEXPORT double putchard(double X) { 437 | fputc((char)X, stderr); 438 | return 0; 439 | } 440 | ``` 441 | 442 | 请注意,对于Windows,我们需要实际导出函数,因为动态符号加载器将使用GetProcAddress查找符号。 443 | 444 | 现在,我们可以使用 `externputchard(x);putchard(120);` 之类的东西向控制台产生简单的输出,在控制台上打印一个小写的“ x”(120是“ x”的ASCII代码)。类似的代码可用于实现文件I / O,控制台输入和Kaleidoscope中的许多其他功能。 445 | 446 | 这样就完成了“Kaleidoscope”教程的“ JIT和优化器”一章。此时,我们可以编译非图灵完整的编程语言,以用户驱动的方式对其进行优化和JIT编译。下一步,我们将研究[通过控制流构造来扩展语言](https://www.tuhaoxin.cn/articles/2019/10/02/1570016138842.html)并一路解决一些有趣的LLVM IR问题。 447 | 448 | ### 完整代码清单 449 | 450 | 这是我们正在运行的示例的完整代码清单,并通过LLVM JIT和优化器进行了增强处理。要构建此示例,请使用: 451 | 452 | ```console 453 | # Compile 454 | clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs all mcjit native` -O3 -o toy 455 | # Run 456 | ./toy 457 | ``` 458 | 以下是完整代码清单: 459 | 460 | [chapter4-Adding-JIT-and-Optimizer-Support.cpp](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/chapter4-Adding-JIT-and-Optimizer-Support.cpp) 461 | 462 | ---- 463 | 参考: [Kaleidoscope: Adding JIT and Optimizer Support](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl04.html) 464 | -------------------------------------------------------------------------------- /blog/8.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第八章:编译为目标代码 2 | 3 | 本文是[使用 LLVM 开发新语言 Kaleidoscope 教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)系列第八章,将生成的代码编译为目标机器代码。 4 | 5 | ### 第八章简介 6 | 7 | 欢迎来到“[使用 LLVM 开发新语言 Kaleidoscope 教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)”教程的第八章。本章介绍如何将我们的中间代码编译为目标代码。 8 | 9 | ### 选择一个目标 10 | 11 | LLVM 支持本地交叉编译。我们可以将代码编译为当前计算机的体系结构,也可以像针对其他体系结构一样轻松地进行编译。在本教程中,我们主要针对当前计算机。 12 | 13 | 为了指定要定位的体系结构,我们使用一个称为 `target Triple` 的字符串。这采用表格形式 `---`(请参阅[交叉编译文档](http://clang.llvm.org/docs/CrossCompilation.html#target-triple))。 14 | 15 | 例如,通过 clang 我们可以看到我们当前的 `target Triple`: 16 | 17 | ```console 18 | $ clang --version | grep Target 19 | Target: x86_64-unknown-linux-gnu 20 | ``` 21 | 22 | 运行此命令可能会显示与您使用的体系结构或操作系统不同的计算机上的某些内容。 23 | 24 | 幸运的是,我们不需要对目标三元组进行硬编码就可以将当前机器作为目标。LLVM 提供了 `sys::getDefaultTargetTriple`,它返回当前计算机的目标三元组。 25 | 26 | ```cpp 27 | auto TargetTriple = sys::getDefaultTargetTriple(); 28 | ``` 29 | 30 | LLVM 不需要我们链接所有目标功能。例如,如果我们仅使用 JIT,则不需要组装打印机。同样,如果我们仅针对某些架构,则只能链接这些架构的功能。 31 | 32 | 在此示例中,我们将初始化所有目标以发出目标代码。 33 | 34 | ``` 35 | InitializeAllTargetInfos(); 36 | InitializeAllTargets(); 37 | InitializeAllTargetMCs(); 38 | InitializeAllAsmParsers(); 39 | InitializeAllAsmPrinters(); 40 | ``` 41 | 42 | 现在,我们就可以使用目标三元组获得 `Target`: 43 | 44 | ```cpp 45 | std::string Error; 46 | auto Target = TargetRegistry::lookupTarget(TargetTriple, Error); 47 | 48 | // Print an error and exit if we couldn't find the requested target. 49 | // This generally occurs if we've forgotten to initialise the 50 | // TargetRegistry or we have a bogus target triple. 51 | if (!Target) { 52 | errs() << Error; 53 | return 1; 54 | } 55 | ``` 56 | 57 | ### 目标机器 58 | 59 | 我们还将需要一个 `TargetMachine` 类。此类提供了我们要定位的机器的完整机器描述。如果我们要针对特定功能(例如 SSE)或特定 CPU(例如英特尔的 Sandylake)。 60 | 61 | 要查看 LLVM 知道哪些功能和 CPU,我们可以使用 `llc` 命令。例如,让我们看一下 x86: 62 | 63 | ```console 64 | $ llvm-as < /dev/null | llc -march=x86 -mattr=help 65 | Available CPUs for this target: 66 | 67 | amdfam10 - Select the amdfam10 processor. 68 | athlon - Select the athlon processor. 69 | athlon-4 - Select the athlon-4 processor. 70 | ... 71 | 72 | Available features for this target: 73 | 74 | 16bit-mode - 16-bit mode (i8086). 75 | 32bit-mode - 32-bit mode (80386). 76 | 3dnow - Enable 3DNow! instructions. 77 | 3dnowa - Enable 3DNow! Athlon instructions. 78 | ... 79 | ``` 80 | 81 | 对于我们的示例,我们将使用不带任何其他功能,选项或重定位模型的通用 CPU。 82 | 83 | ```cpp 84 | auto CPU = "generic"; 85 | auto Features = ""; 86 | 87 | TargetOptions opt; 88 | auto RM = Optional(); 89 | auto TargetMachine = Target->createTargetMachine(TargetTriple, CPU, Features, opt, RM); 90 | ``` 91 | 92 | ### 配置 Module 93 | 94 | 现在,我们可以配置模块,以指定目标和数据布局。这不是严格必要的,但是[前端性能指南](https://llvm.org/docs/tutorial/Frontend/PerformanceTips.html)建议这样做。优化可以从了解目标和数据布局中受益。 95 | 96 | ```cpp 97 | TheModule->setDataLayout(TargetMachine->createDataLayout()); 98 | TheModule->setTargetTriple(TargetTriple); 99 | ``` 100 | 101 | ### 生成目标代码 102 | 103 | 我们准备生成目标代码!我们先定义要将文件写入的位置: 104 | 105 | ```cpp 106 | auto Filename = "output.o"; 107 | std::error_code EC; 108 | raw_fd_ostream dest(Filename, EC, sys::fs::OF_None); 109 | 110 | if (EC) { 111 | errs() << "Could not open file: " << EC.message(); 112 | return 1; 113 | } 114 | ``` 115 | 116 | 最后,我们定义一个发出目标代码的过程,然后运行该 pass: 117 | 118 | ```cpp 119 | legacy::PassManager pass; 120 | auto FileType = TargetMachine::CGFT_ObjectFile; 121 | 122 | if (TargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType)) { 123 | errs() << "TargetMachine can't emit a file of this type"; 124 | return 1; 125 | } 126 | 127 | pass.run(*TheModule); 128 | dest.flush(); 129 | ``` 130 | 131 | ### 组合起来 132 | 133 | 它行得通吗?试一试吧。我们需要编译代码,但是请注意,to 的参数 `llvm-config` 与前面的章节不同。 134 | 135 | ```console 136 | $ clang++ -g -O3 chapter8-Compiling-to-Object-Code.cpp `llvm-config --cxxflags --ldflags --system-libs --libs all` -o toy 137 | ``` 138 | 139 | 让我们运行它,并定义一个简单的 `average` 函数。完成后按 Ctrl-D。 140 | 141 | ``` 142 | $ ./toy 143 | ready> def average(x y) (x + y) * 0.5; 144 | ^D 145 | Wrote output.o 146 | ``` 147 | 148 | 我们有一个目标文件!为了测试它,让我们编写一个简单的程序并将其与我们的输出链接。这是源代码: 149 | 150 | ```cpp 151 | #include 152 | 153 | extern "C" { 154 | double average(double, double); 155 | } 156 | 157 | int main() { 158 | std::cout << "average of 3.0 and 4.0: " << average(3.0, 4.0) << std::endl; 159 | } 160 | ``` 161 | 162 | 我们将程序链接到 output.o,并检查结果是否符合预期: 163 | 164 | ```console 165 | $ clang++ main.cpp output.o -o main 166 | $ ./main 167 | average of 3.0 and 4.0: 3.5 168 | ``` 169 | 170 | ### 完整代码集合 171 | 172 | [chapter8-Compiling-to-Object-Code.cpp](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/chapter8-Compiling-to-Object-Code.cpp) 173 | 174 | --- 175 | 176 | 参考: [Kaleidoscope: Compiling to Object Code](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl08.html) 177 | -------------------------------------------------------------------------------- /blog/9.md: -------------------------------------------------------------------------------- 1 | ## Kaleidoscope系列第九章:增加调试信息 2 | 3 | 本文是[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)系列第九章,将为Kaleidoscope添加调试信息,帮助高效开发新语言。 4 | 5 | ### 第九章简介 6 | 7 | 欢迎来到“[使用LLVM开发新语言Kaleidoscope教程](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/blog/0.md)”教程的第九章。在第一章至第八章中,我们构建了一种带有函数和变量的体面的小型编程语言。这时会有一个问题,那就是如果出了问题怎么办,如何调试程序? 8 | 9 | 源代码级调试使用格式化的数据,这些数据可帮助调试器将二进制文件和计算机的状态转换回程序员编写的源代码。在LLVM中,我们通常使用一种称为[DWARF](http://dwarfstd.org/)的格式。DWARF是一种紧凑的编码,表示类型,源位置和可变位置。 10 | 11 | 本章主要介绍为支持调试信息而必须添加到编程语言中的各种内容,以及如何将其转换为DWARF。 12 | 13 | 警告:目前我们无法通过JIT进行调试,因此我们需要将程序编译为小型且独立的程序。在此过程中,我们将对语言的运行方式以及程序的编译方式进行一些修改。这意味着我们将拥有一个用Kaleidoscope编写的简单程序而不是交互式JIT的源文件。确实存在一个限制,即我们一次只能有一个“顶层”命令来减少必要的更改数量。 14 | 15 | 这是我们将要编译的示例程序: 16 | 17 | ``` 18 | def fib(x) 19 | if x < 3 then 20 | 1 21 | else 22 | fib(x-1)+fib(x-2); 23 | 24 | fib(10) 25 | ``` 26 | 27 | ### 为什么很难? 28 | 29 | 调试信息是一个棘手的问题,其原因有很多-大多围绕优化的代码。首先,优化使保持源位置更加困难。在LLVM IR中,我们在指令上保留每个IR级别指令的原始源位置。优化过程应该保留新创建指令的源位置,但是合并的指令只能保留一个位置-这可能会导致在逐步浏览优化程序时跳来跳去。其次,优化可以以优化的方式移动变量,与其他变量在内存中共享或难以跟踪的方式移动变量。出于本教程的目的,我们将避免优化(如我们将在下一组补丁中看到的那样)。 30 | 31 | ### AOT编译模型 32 | 33 | 为了仅强调将调试信息添加到源语言中的各个方面,而不必担心JIT调试的复杂性,我们将对Kaleidoscope进行一些更改,以支持将前端发出的IR编译为一个简单的独立程序,你可以执行以下代码,调试并查看结果。 34 | 35 | 首先,我们使包含顶层语句的匿名函数成为“ main”: 36 | 37 | ```diff 38 | - auto Proto = std::make_unique("", std::vector()); 39 | + auto Proto = std::make_unique("main", std::vector()); 40 | ``` 41 | 42 | 只需更改名称即可。 43 | 44 | 然后,我们将删除存在的命令行代码: 45 | 46 | ```diff 47 | @@ -1129,7 +1129,6 @@ static void HandleTopLevelExpression() { 48 | /// top ::= definition | external | expression | ';' 49 | static void MainLoop() { 50 | while (1) { 51 | - fprintf(stderr, "ready> "); 52 | switch (CurTok) { 53 | case tok_eof: 54 | return; 55 | @@ -1184,7 +1183,6 @@ int main() { 56 | BinopPrecedence['*'] = 40; // highest. 57 | 58 | // Prime the first token. 59 | - fprintf(stderr, "ready> "); 60 | getNextToken(); 61 | ``` 62 | 63 | 最后,我们将禁用所有优化过程和JIT,以便在我们完成解析和生成代码后,唯一有区别的事情是LLVM IR变为标准错误: 64 | 65 | ```diff 66 | @@ -1108,17 +1108,8 @@ static void HandleExtern() { 67 | static void HandleTopLevelExpression() { 68 | // Evaluate a top-level expression into an anonymous function. 69 | if (auto FnAST = ParseTopLevelExpr()) { 70 | - if (auto *FnIR = FnAST->codegen()) { 71 | - // We're just doing this to make sure it executes. 72 | - TheExecutionEngine->finalizeObject(); 73 | - // JIT the function, returning a function pointer. 74 | - void *FPtr = TheExecutionEngine->getPointerToFunction(FnIR); 75 | - 76 | - // Cast it to the right type (takes no arguments, returns a double) so we 77 | - // can call it as a native function. 78 | - double (*FP)() = (double (*)())(intptr_t)FPtr; 79 | - // Ignore the return value for this. 80 | - (void)FP; 81 | + if (!F->codegen()) { 82 | + fprintf(stderr, "Error generating code for top level expr"); 83 | } 84 | } else { 85 | // Skip token for error recovery. 86 | @@ -1439,11 +1459,11 @@ int main() { 87 | // target lays out data structures. 88 | TheModule->setDataLayout(TheExecutionEngine->getDataLayout()); 89 | OurFPM.add(new DataLayoutPass()); 90 | +#if 0 91 | OurFPM.add(createBasicAliasAnalysisPass()); 92 | // Promote allocas to registers. 93 | OurFPM.add(createPromoteMemoryToRegisterPass()); 94 | @@ -1218,7 +1210,7 @@ int main() { 95 | OurFPM.add(createGVNPass()); 96 | // Simplify the control flow graph (deleting unreachable blocks, etc). 97 | OurFPM.add(createCFGSimplificationPass()); 98 | - 99 | + #endif 100 | OurFPM.doInitialization(); 101 | 102 | // Set the global so the code gen can use this. 103 | ``` 104 | 105 | 这些相对较小的更改使我们可以通过以下命令行将Kaleidoscope语言片段编译为可执行程序: 106 | 107 | ```console 108 | Kaleidoscope-Ch9 < fib.ks | & clang -x ir - 109 | ``` 110 | 111 | 它在当前工作目录中给出a.out / a.exe。 112 | 113 | ### 编译单元 114 | 115 | DWARF中一段代码的顶级容器是编译单元。它包含单个翻译单元的类型和功能数据(阅读:源代码的一个文件)。因此,我们要做的第一件事是为fib.ks文件构造一个编译单元。 116 | 117 | ### DWARF Emisson 设置 118 | 119 | 与 `IRBuilder` 该类相似,我们有一个[DIBuilder](http://llvm.org/doxygen/classllvm_1_1DIBuilder.html)类,该类有助于为LLVM IR文件构造调试元数据。`IRBuilder` 与LLVM IR类似,它对应1:1,但名称更好。使用它确实需要您比使用DWARF术语更熟悉DAMRF术语 `IRBuilder` 和 `Instruction` 名称,但是如果你通读了有关[元数据格式](http://llvm.org/docs/SourceLevelDebugging.html)的常规文档,则应该更加清楚。我们将使用此类构造所有的IR级别描述。它的构建需要一个模块,因此我们需要在构建模块后不久对其进行构建。我们将其保留为全局静态变量,以使其易于使用。 120 | 121 | 接下来,我们将创建一个小容器来缓存一些常用数据。第一个是我们的编译单元,但是我们也将为我们的一种类型编写一些代码,因为我们不必担心多种类型的表达式: 122 | 123 | ```cpp 124 | static DIBuilder *DBuilder; 125 | 126 | struct DebugInfo { 127 | DICompileUnit *TheCU; 128 | DIType *DblTy; 129 | 130 | DIType *getDoubleTy(); 131 | } KSDbgInfo; 132 | 133 | DIType *DebugInfo::getDoubleTy() { 134 | if (DblTy) 135 | return DblTy; 136 | 137 | DblTy = DBuilder->createBasicType("double", 64, dwarf::DW_ATE_float); 138 | return DblTy; 139 | } 140 | ``` 141 | 142 | 然后在构建模块 `main` 时: 143 | 144 | ```cpp 145 | DBuilder = new DIBuilder(*TheModule); 146 | 147 | KSDbgInfo.TheCU = DBuilder->createCompileUnit( 148 | dwarf::DW_LANG_C, DBuilder->createFile("fib.ks", "."), 149 | "Kaleidoscope Compiler", 0, "", 0); 150 | ``` 151 | 152 | 这里有几件事要注意。首先,当我们为称为Kaleidoscope的语言生成编译单元时,我们将语言常量用于C。这是因为调试器不一定会理解其无法识别的语言的调用约定或默认ABI,因此我们遵循LLVM代码生成中的C ABI,因此它是最接近准确的东西。这确保了我们实际上可以从调试器中调用函数并使它们执行。其次,您会在的调用中看到“ fib.ks” `createCompileUnit`。这是默认的硬编码值,因为我们使用外壳重定向将源代码放入Kaleidoscope编译器中。在通常的前端中,通常有一个输入文件名,它将输入该文件名。 153 | 154 | 通过DIBuilder发出调试信息的最后一件事是,我们需要“完成”调试信息。原因是DIBuilder的基础API的一部分,但请确保在main末尾附近,在转储模块之前执行此操作: 155 | 156 | ```cpp 157 | DBuilder->finalize(); 158 | ``` 159 | 160 | ### 函数 161 | 162 | 现在我们有了 `CompileUnit` 和我们的源位置,可以将函数定义添加到调试信息中。因此,我们在 `PrototypeAST::codegen()` 中添加了几行代码来描述子程序的上下文(在本例中为“文件”)以及函数本身的实际定义。 163 | 164 | 所以上下文: 165 | 166 | ```cpp 167 | DIFile *Unit = DBuilder->createFile(KSDbgInfo.TheCU.getFilename(), 168 | KSDbgInfo.TheCU.getDirectory()); 169 | ``` 170 | 171 | 给我们一个DIFile并询问我们上面创建 `CompileUnit` 的当前目录和文件名。然后,现在,我们使用一些源位置0(因为AST当前没有源位置信息)并构造函数定义: 172 | 173 | ```cpp 174 | DIScope *FContext = Unit; 175 | unsigned LineNo = 0; 176 | unsigned ScopeLine = 0; 177 | DISubprogram *SP = DBuilder->createFunction( 178 | FContext, P.getName(), StringRef(), Unit, LineNo, 179 | CreateFunctionType(TheFunction->arg_size(), Unit), 180 | false /* internal linkage */, true /* definition */, ScopeLine, 181 | DINode::FlagPrototyped, false); 182 | TheFunction->setSubprogram(SP); 183 | ``` 184 | 185 | 现在我们有了一个DISubprogram,其中包含对该函数所有元数据的引用。 186 | 187 | ### 源代码位置 188 | 189 | 调试信息最重要的是准确的源代码位置-这使我们可以将源代码映射回去。但是,我们有一个问题,Kaleidoscope确实在词法分析器或解析器中没有任何源位置信息,因此我们需要添加它。 190 | 191 | ```cpp 192 | struct SourceLocation { 193 | int Line; 194 | int Col; 195 | }; 196 | static SourceLocation CurLoc; 197 | static SourceLocation LexLoc = {1, 0}; 198 | 199 | static int advance() { 200 | int LastChar = getchar(); 201 | 202 | if (LastChar == '\n' || LastChar == '\r') { 203 | LexLoc.Line++; 204 | LexLoc.Col = 0; 205 | } else 206 | LexLoc.Col++; 207 | return LastChar; 208 | } 209 | ``` 210 | 211 | 在这组代码中,我们添加了一些有关如何跟踪“源文件”的行和列的功能。在对每个标记进行词法分析时,我们将当前当前的“词法位置”设置为标记词开头的各种行和列。为此 `getchar()`,我们使用新的`advance()`跟踪信息的方法来覆盖以前的所有调用,然后将所有位置添加到所有AST类中: 212 | 213 | ```cpp 214 | class ExprAST { 215 | SourceLocation Loc; 216 | 217 | public: 218 | ExprAST(SourceLocation Loc = CurLoc) : Loc(Loc) {} 219 | virtual ~ExprAST() {} 220 | virtual Value* codegen() = 0; 221 | int getLine() const { return Loc.Line; } 222 | int getCol() const { return Loc.Col; } 223 | virtual raw_ostream &dump(raw_ostream &out, int ind) { 224 | return out << ':' << getLine() << ':' << getCol() << '\n'; 225 | } 226 | ``` 227 | 228 | 当我们创建一个新的表达式时,我们会忽略: 229 | 230 | ```cpp 231 | LHS = std::make_unique(BinLoc, BinOp, std::move(LHS), 232 | std::move(RHS)); 233 | ``` 234 | 235 | 给我们每个表达式和变量的位置。 236 | 237 | 为了确保每条指令都能获得正确的源位置信息,我们必须告诉 `Builder` 我们何时位于新的源位置。我们为此使用一个小的辅助函数: 238 | 239 | ```cpp 240 | void DebugInfo::emitLocation(ExprAST *AST) { 241 | DIScope *Scope; 242 | if (LexicalBlocks.empty()) 243 | Scope = TheCU; 244 | else 245 | Scope = LexicalBlocks.back(); 246 | Builder.SetCurrentDebugLocation( 247 | DebugLoc::get(AST->getLine(), AST->getCol(), Scope)); 248 | } 249 | ``` 250 | 251 | 这既可以告诉`IRBuilder`我们主要的位置,也可以告诉我们所处的作用域。该作用域可以在编译单元级别,也可以是与当前函数类似的最接近的词法块。为了表示这一点,我们创建了一个范围堆栈: 252 | 253 | ```cpp 254 | std::vector LexicalBlocks; 255 | ``` 256 | 257 | 当我们开始为每个函数生成代码时,将作用域(函数)推到栈顶: 258 | 259 | ```cpp 260 | KSDbgInfo.LexicalBlocks.push_back(SP); 261 | ``` 262 | 263 | 同样,我们可能不会忘记在函数的代码生成结束时将范围弹出弹出范围堆栈: 264 | 265 | ```cpp 266 | // Pop off the lexical block for the function since we added it 267 | // unconditionally. 268 | KSDbgInfo.LexicalBlocks.pop_back(); 269 | ``` 270 | 271 | 然后,确保每次开始为新的AST对象生成代码时都发出该位置: 272 | 273 | `KSDbgInfo.emitLocation(this);` 274 | 275 | ### 变量 276 | 277 | 现在我们有了函数,我们需要能够打印出范围内的变量。我们首先设置函数参数,以便获得不错的回溯并查看如何调用函数。实现它不需要很多代码,并且通常只需要在创建中的allocas参数时处理 `FunctionAST::codegen`。 278 | 279 | ```cpp 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 | DebugLoc::get(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 | ```cpp 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 | ```cpp 318 | KSDbgInfo.emitLocation(Body.get()); 319 | ``` 320 | 321 | 这样,我们就有足够的调试信息来设置函数中的断点,打印出参数变量和调用函数。只需几行简单的代码就可以调试的不错了! 322 | 323 | ### 完整代码清单 324 | 325 | 这是我们正在运行的示例的完整代码清单,其中增加了调试信息。想要运行并演示此示例,请使用以下命令: 326 | 327 | ```console 328 | # Compile 329 | clang++ -g chapter9-Adding-Debug-Information.cpp `llvm-config --cxxflags --ldflags --system-libs --libs all mcjit native` -O3 -o toy 330 | # Run 331 | ./toy 332 | ``` 333 | 334 | 以下完整代码清单: 335 | 336 | [chapter9-Adding-Debug-Information.cpp 337 | ](https://github.com/Hanseltu/kaleidoscope-tutorial/blob/master/chapter9-Adding-Debug-Information.cpp) 338 | 339 | --- 340 | 参考: [Kaleidoscope: Adding Debug Information](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl09.html) 341 | -------------------------------------------------------------------------------- /chapter2-Implementing-a-Parser-and-AST.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/STLExtras.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //===----------------------------------------------------------------------===// 12 | // Lexer 13 | //===----------------------------------------------------------------------===// 14 | 15 | // The lexer returns tokens [0-255] if it is an unknown character, otherwise one 16 | // of these for known things. 17 | enum Token { 18 | tok_eof = -1, 19 | 20 | // commands 21 | tok_def = -2, 22 | tok_extern = -3, 23 | 24 | // primary 25 | tok_identifier = -4, 26 | tok_number = -5 27 | }; 28 | 29 | static std::string IdentifierStr; // Filled in if tok_identifier 30 | static double NumVal; // Filled in if tok_number 31 | 32 | /// gettok - Return the next token from standard input. 33 | static int gettok() { 34 | static int LastChar = ' '; 35 | 36 | // Skip any whitespace. 37 | while (isspace(LastChar)) 38 | LastChar = getchar(); 39 | 40 | if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]* 41 | IdentifierStr = LastChar; 42 | while (isalnum((LastChar = getchar()))) 43 | IdentifierStr += LastChar; 44 | 45 | if (IdentifierStr == "def") 46 | return tok_def; 47 | if (IdentifierStr == "extern") 48 | return tok_extern; 49 | return tok_identifier; 50 | } 51 | 52 | if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+ 53 | std::string NumStr; 54 | do { 55 | NumStr += LastChar; 56 | LastChar = getchar(); 57 | } while (isdigit(LastChar) || LastChar == '.'); 58 | 59 | NumVal = strtod(NumStr.c_str(), nullptr); 60 | return tok_number; 61 | } 62 | 63 | if (LastChar == '#') { 64 | // Comment until end of line. 65 | do 66 | LastChar = getchar(); 67 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 68 | 69 | if (LastChar != EOF) 70 | return gettok(); 71 | } 72 | 73 | // Check for end of file. Don't eat the EOF. 74 | if (LastChar == EOF) 75 | return tok_eof; 76 | 77 | // Otherwise, just return the character as its ascii value. 78 | int ThisChar = LastChar; 79 | LastChar = getchar(); 80 | return ThisChar; 81 | } 82 | 83 | //===----------------------------------------------------------------------===// 84 | // Abstract Syntax Tree (aka Parse Tree) 85 | //===----------------------------------------------------------------------===// 86 | 87 | namespace { 88 | 89 | /// ExprAST - Base class for all expression nodes. 90 | class ExprAST { 91 | public: 92 | virtual ~ExprAST() = default; 93 | }; 94 | 95 | /// NumberExprAST - Expression class for numeric literals like "1.0". 96 | class NumberExprAST : public ExprAST { 97 | double Val; 98 | 99 | public: 100 | NumberExprAST(double Val) : Val(Val) {} 101 | }; 102 | 103 | /// VariableExprAST - Expression class for referencing a variable, like "a". 104 | class VariableExprAST : public ExprAST { 105 | std::string Name; 106 | 107 | public: 108 | VariableExprAST(const std::string &Name) : Name(Name) {} 109 | }; 110 | 111 | /// BinaryExprAST - Expression class for a binary operator. 112 | class BinaryExprAST : public ExprAST { 113 | char Op; 114 | std::unique_ptr LHS, RHS; 115 | 116 | public: 117 | BinaryExprAST(char Op, std::unique_ptr LHS, 118 | std::unique_ptr RHS) 119 | : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 120 | }; 121 | 122 | /// CallExprAST - Expression class for function calls. 123 | class CallExprAST : public ExprAST { 124 | std::string Callee; 125 | std::vector> Args; 126 | 127 | public: 128 | CallExprAST(const std::string &Callee, 129 | std::vector> Args) 130 | : Callee(Callee), Args(std::move(Args)) {} 131 | }; 132 | 133 | /// PrototypeAST - This class represents the "prototype" for a function, 134 | /// which captures its name, and its argument names (thus implicitly the number 135 | /// of arguments the function takes). 136 | class PrototypeAST { 137 | std::string Name; 138 | std::vector Args; 139 | 140 | public: 141 | PrototypeAST(const std::string &Name, std::vector Args) 142 | : Name(Name), Args(std::move(Args)) {} 143 | 144 | const std::string &getName() const { return Name; } 145 | }; 146 | 147 | /// FunctionAST - This class represents a function definition itself. 148 | class FunctionAST { 149 | std::unique_ptr Proto; 150 | std::unique_ptr Body; 151 | 152 | public: 153 | FunctionAST(std::unique_ptr Proto, 154 | std::unique_ptr Body) 155 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 156 | }; 157 | 158 | } // end anonymous namespace 159 | 160 | //===----------------------------------------------------------------------===// 161 | // Parser 162 | //===----------------------------------------------------------------------===// 163 | 164 | /// CurTok/getNextToken - Provide a simple token buffer. CurTok is the current 165 | /// token the parser is looking at. getNextToken reads another token from the 166 | /// lexer and updates CurTok with its results. 167 | static int CurTok; 168 | static int getNextToken() { return CurTok = gettok(); } 169 | 170 | /// BinopPrecedence - This holds the precedence for each binary operator that is 171 | /// defined. 172 | static std::map BinopPrecedence; 173 | 174 | /// GetTokPrecedence - Get the precedence of the pending binary operator token. 175 | static int GetTokPrecedence() { 176 | if (!isascii(CurTok)) 177 | return -1; 178 | 179 | // Make sure it's a declared binop. 180 | int TokPrec = BinopPrecedence[CurTok]; 181 | if (TokPrec <= 0) 182 | return -1; 183 | return TokPrec; 184 | } 185 | 186 | /// LogError* - These are little helper functions for error handling. 187 | std::unique_ptr LogError(const char *Str) { 188 | fprintf(stderr, "Error: %s\n", Str); 189 | return nullptr; 190 | } 191 | std::unique_ptr LogErrorP(const char *Str) { 192 | LogError(Str); 193 | return nullptr; 194 | } 195 | 196 | static std::unique_ptr ParseExpression(); 197 | 198 | /// numberexpr ::= number 199 | static std::unique_ptr ParseNumberExpr() { 200 | auto Result = llvm::make_unique(NumVal); 201 | getNextToken(); // consume the number 202 | return std::move(Result); 203 | } 204 | 205 | /// parenexpr ::= '(' expression ')' 206 | static std::unique_ptr ParseParenExpr() { 207 | getNextToken(); // eat (. 208 | auto V = ParseExpression(); 209 | if (!V) 210 | return nullptr; 211 | 212 | if (CurTok != ')') 213 | return LogError("expected ')'"); 214 | getNextToken(); // eat ). 215 | return V; 216 | } 217 | 218 | /// identifierexpr 219 | /// ::= identifier 220 | /// ::= identifier '(' expression* ')' 221 | static std::unique_ptr ParseIdentifierExpr() { 222 | std::string IdName = IdentifierStr; 223 | 224 | getNextToken(); // eat identifier. 225 | 226 | if (CurTok != '(') // Simple variable ref. 227 | return llvm::make_unique(IdName); 228 | 229 | // Call. 230 | getNextToken(); // eat ( 231 | std::vector> Args; 232 | if (CurTok != ')') { 233 | while (true) { 234 | if (auto Arg = ParseExpression()) 235 | Args.push_back(std::move(Arg)); 236 | else 237 | return nullptr; 238 | 239 | if (CurTok == ')') 240 | break; 241 | 242 | if (CurTok != ',') 243 | return LogError("Expected ')' or ',' in argument list"); 244 | getNextToken(); 245 | } 246 | } 247 | 248 | // Eat the ')'. 249 | getNextToken(); 250 | 251 | return llvm::make_unique(IdName, std::move(Args)); 252 | } 253 | 254 | /// primary 255 | /// ::= identifierexpr 256 | /// ::= numberexpr 257 | /// ::= parenexpr 258 | static std::unique_ptr ParsePrimary() { 259 | switch (CurTok) { 260 | default: 261 | return LogError("unknown token when expecting an expression"); 262 | case tok_identifier: 263 | return ParseIdentifierExpr(); 264 | case tok_number: 265 | return ParseNumberExpr(); 266 | case '(': 267 | return ParseParenExpr(); 268 | } 269 | } 270 | 271 | /// binoprhs 272 | /// ::= ('+' primary)* 273 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 274 | std::unique_ptr LHS) { 275 | // If this is a binop, find its precedence. 276 | while (true) { 277 | int TokPrec = GetTokPrecedence(); 278 | 279 | // If this is a binop that binds at least as tightly as the current binop, 280 | // consume it, otherwise we are done. 281 | if (TokPrec < ExprPrec) 282 | return LHS; 283 | 284 | // Okay, we know this is a binop. 285 | int BinOp = CurTok; 286 | getNextToken(); // eat binop 287 | 288 | // Parse the primary expression after the binary operator. 289 | auto RHS = ParsePrimary(); 290 | if (!RHS) 291 | return nullptr; 292 | 293 | // If BinOp binds less tightly with RHS than the operator after RHS, let 294 | // the pending operator take RHS as its LHS. 295 | int NextPrec = GetTokPrecedence(); 296 | if (TokPrec < NextPrec) { 297 | RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS)); 298 | if (!RHS) 299 | return nullptr; 300 | } 301 | 302 | // Merge LHS/RHS. 303 | LHS = llvm::make_unique(BinOp, std::move(LHS), 304 | std::move(RHS)); 305 | } 306 | } 307 | 308 | /// expression 309 | /// ::= primary binoprhs 310 | /// 311 | static std::unique_ptr ParseExpression() { 312 | auto LHS = ParsePrimary(); 313 | if (!LHS) 314 | return nullptr; 315 | 316 | return ParseBinOpRHS(0, std::move(LHS)); 317 | } 318 | 319 | /// prototype 320 | /// ::= id '(' id* ')' 321 | static std::unique_ptr ParsePrototype() { 322 | if (CurTok != tok_identifier) 323 | return LogErrorP("Expected function name in prototype"); 324 | 325 | std::string FnName = IdentifierStr; 326 | getNextToken(); 327 | 328 | if (CurTok != '(') 329 | return LogErrorP("Expected '(' in prototype"); 330 | 331 | std::vector ArgNames; 332 | while (getNextToken() == tok_identifier) 333 | ArgNames.push_back(IdentifierStr); 334 | if (CurTok != ')') 335 | return LogErrorP("Expected ')' in prototype"); 336 | 337 | // success. 338 | getNextToken(); // eat ')'. 339 | 340 | return llvm::make_unique(FnName, std::move(ArgNames)); 341 | } 342 | 343 | /// definition ::= 'def' prototype expression 344 | static std::unique_ptr ParseDefinition() { 345 | getNextToken(); // eat def. 346 | auto Proto = ParsePrototype(); 347 | if (!Proto) 348 | return nullptr; 349 | 350 | if (auto E = ParseExpression()) 351 | return llvm::make_unique(std::move(Proto), std::move(E)); 352 | return nullptr; 353 | } 354 | 355 | /// toplevelexpr ::= expression 356 | static std::unique_ptr ParseTopLevelExpr() { 357 | if (auto E = ParseExpression()) { 358 | // Make an anonymous proto. 359 | auto Proto = llvm::make_unique("__anon_expr", 360 | std::vector()); 361 | return llvm::make_unique(std::move(Proto), std::move(E)); 362 | } 363 | return nullptr; 364 | } 365 | 366 | /// external ::= 'extern' prototype 367 | static std::unique_ptr ParseExtern() { 368 | getNextToken(); // eat extern. 369 | return ParsePrototype(); 370 | } 371 | 372 | //===----------------------------------------------------------------------===// 373 | // Top-Level parsing 374 | //===----------------------------------------------------------------------===// 375 | 376 | static void HandleDefinition() { 377 | if (ParseDefinition()) { 378 | fprintf(stderr, "Parsed a function definition.\n"); 379 | } else { 380 | // Skip token for error recovery. 381 | getNextToken(); 382 | } 383 | } 384 | 385 | static void HandleExtern() { 386 | if (ParseExtern()) { 387 | fprintf(stderr, "Parsed an extern\n"); 388 | } else { 389 | // Skip token for error recovery. 390 | getNextToken(); 391 | } 392 | } 393 | 394 | static void HandleTopLevelExpression() { 395 | // Evaluate a top-level expression into an anonymous function. 396 | if (ParseTopLevelExpr()) { 397 | fprintf(stderr, "Parsed a top-level expr\n"); 398 | } else { 399 | // Skip token for error recovery. 400 | getNextToken(); 401 | } 402 | } 403 | 404 | /// top ::= definition | external | expression | ';' 405 | static void MainLoop() { 406 | while (true) { 407 | fprintf(stderr, "ready> "); 408 | switch (CurTok) { 409 | case tok_eof: 410 | return; 411 | case ';': // ignore top-level semicolons. 412 | getNextToken(); 413 | break; 414 | case tok_def: 415 | HandleDefinition(); 416 | break; 417 | case tok_extern: 418 | HandleExtern(); 419 | break; 420 | default: 421 | HandleTopLevelExpression(); 422 | break; 423 | } 424 | } 425 | } 426 | 427 | //===----------------------------------------------------------------------===// 428 | // Main driver code. 429 | //===----------------------------------------------------------------------===// 430 | 431 | int main() { 432 | // Install standard binary operators. 433 | // 1 is lowest precedence. 434 | BinopPrecedence['<'] = 10; 435 | BinopPrecedence['+'] = 20; 436 | BinopPrecedence['-'] = 20; 437 | BinopPrecedence['*'] = 40; // highest. 438 | 439 | // Prime the first token. 440 | fprintf(stderr, "ready> "); 441 | getNextToken(); 442 | 443 | // Run the main "interpreter loop" now. 444 | MainLoop(); 445 | 446 | return 0; 447 | } 448 | -------------------------------------------------------------------------------- /chapter3-Code-generation-to-LLVM-IR.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/APFloat.h" 2 | #include "llvm/ADT/STLExtras.h" 3 | #include "llvm/IR/BasicBlock.h" 4 | #include "llvm/IR/Constants.h" 5 | #include "llvm/IR/DerivedTypes.h" 6 | #include "llvm/IR/Function.h" 7 | #include "llvm/IR/IRBuilder.h" 8 | #include "llvm/IR/LLVMContext.h" 9 | #include "llvm/IR/Module.h" 10 | #include "llvm/IR/Type.h" 11 | #include "llvm/IR/Verifier.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace llvm; 22 | 23 | //===----------------------------------------------------------------------===// 24 | // Lexer 25 | //===----------------------------------------------------------------------===// 26 | 27 | // The lexer returns tokens [0-255] if it is an unknown character, otherwise one 28 | // of these for known things. 29 | enum Token { 30 | tok_eof = -1, 31 | 32 | // commands 33 | tok_def = -2, 34 | tok_extern = -3, 35 | 36 | // primary 37 | tok_identifier = -4, 38 | tok_number = -5 39 | }; 40 | 41 | static std::string IdentifierStr; // Filled in if tok_identifier 42 | static double NumVal; // Filled in if tok_number 43 | 44 | /// gettok - Return the next token from standard input. 45 | static int gettok() { 46 | static int LastChar = ' '; 47 | 48 | // Skip any whitespace. 49 | while (isspace(LastChar)) 50 | LastChar = getchar(); 51 | 52 | if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]* 53 | IdentifierStr = LastChar; 54 | while (isalnum((LastChar = getchar()))) 55 | IdentifierStr += LastChar; 56 | 57 | if (IdentifierStr == "def") 58 | return tok_def; 59 | if (IdentifierStr == "extern") 60 | return tok_extern; 61 | return tok_identifier; 62 | } 63 | 64 | if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+ 65 | std::string NumStr; 66 | do { 67 | NumStr += LastChar; 68 | LastChar = getchar(); 69 | } while (isdigit(LastChar) || LastChar == '.'); 70 | 71 | NumVal = strtod(NumStr.c_str(), nullptr); 72 | return tok_number; 73 | } 74 | 75 | if (LastChar == '#') { 76 | // Comment until end of line. 77 | do 78 | LastChar = getchar(); 79 | while (LastChar != EOF && LastChar != '\n' && LastChar != '\r'); 80 | 81 | if (LastChar != EOF) 82 | return gettok(); 83 | } 84 | 85 | // Check for end of file. Don't eat the EOF. 86 | if (LastChar == EOF) 87 | return tok_eof; 88 | 89 | // Otherwise, just return the character as its ascii value. 90 | int ThisChar = LastChar; 91 | LastChar = getchar(); 92 | return ThisChar; 93 | } 94 | 95 | //===----------------------------------------------------------------------===// 96 | // Abstract Syntax Tree (aka Parse Tree) 97 | //===----------------------------------------------------------------------===// 98 | 99 | namespace { 100 | 101 | /// ExprAST - Base class for all expression nodes. 102 | class ExprAST { 103 | public: 104 | virtual ~ExprAST() = default; 105 | 106 | virtual Value *codegen() = 0; 107 | }; 108 | 109 | /// NumberExprAST - Expression class for numeric literals like "1.0". 110 | class NumberExprAST : public ExprAST { 111 | double Val; 112 | 113 | public: 114 | NumberExprAST(double Val) : Val(Val) {} 115 | 116 | Value *codegen() override; 117 | }; 118 | 119 | /// VariableExprAST - Expression class for referencing a variable, like "a". 120 | class VariableExprAST : public ExprAST { 121 | std::string Name; 122 | 123 | public: 124 | VariableExprAST(const std::string &Name) : Name(Name) {} 125 | 126 | Value *codegen() override; 127 | }; 128 | 129 | /// BinaryExprAST - Expression class for a binary operator. 130 | class BinaryExprAST : public ExprAST { 131 | char Op; 132 | std::unique_ptr LHS, RHS; 133 | 134 | public: 135 | BinaryExprAST(char Op, std::unique_ptr LHS, 136 | std::unique_ptr RHS) 137 | : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} 138 | 139 | Value *codegen() override; 140 | }; 141 | 142 | /// CallExprAST - Expression class for function calls. 143 | class CallExprAST : public ExprAST { 144 | std::string Callee; 145 | std::vector> Args; 146 | 147 | public: 148 | CallExprAST(const std::string &Callee, 149 | std::vector> Args) 150 | : Callee(Callee), Args(std::move(Args)) {} 151 | 152 | Value *codegen() override; 153 | }; 154 | 155 | /// PrototypeAST - This class represents the "prototype" for a function, 156 | /// which captures its name, and its argument names (thus implicitly the number 157 | /// of arguments the function takes). 158 | class PrototypeAST { 159 | std::string Name; 160 | std::vector Args; 161 | 162 | public: 163 | PrototypeAST(const std::string &Name, std::vector Args) 164 | : Name(Name), Args(std::move(Args)) {} 165 | 166 | Function *codegen(); 167 | const std::string &getName() const { return Name; } 168 | }; 169 | 170 | /// FunctionAST - This class represents a function definition itself. 171 | class FunctionAST { 172 | std::unique_ptr Proto; 173 | std::unique_ptr Body; 174 | 175 | public: 176 | FunctionAST(std::unique_ptr Proto, 177 | std::unique_ptr Body) 178 | : Proto(std::move(Proto)), Body(std::move(Body)) {} 179 | 180 | Function *codegen(); 181 | }; 182 | 183 | } // end anonymous namespace 184 | 185 | //===----------------------------------------------------------------------===// 186 | // Parser 187 | //===----------------------------------------------------------------------===// 188 | 189 | /// CurTok/getNextToken - Provide a simple token buffer. CurTok is the current 190 | /// token the parser is looking at. getNextToken reads another token from the 191 | /// lexer and updates CurTok with its results. 192 | static int CurTok; 193 | static int getNextToken() { return CurTok = gettok(); } 194 | 195 | /// BinopPrecedence - This holds the precedence for each binary operator that is 196 | /// defined. 197 | static std::map BinopPrecedence; 198 | 199 | /// GetTokPrecedence - Get the precedence of the pending binary operator token. 200 | static int GetTokPrecedence() { 201 | if (!isascii(CurTok)) 202 | return -1; 203 | 204 | // Make sure it's a declared binop. 205 | int TokPrec = BinopPrecedence[CurTok]; 206 | if (TokPrec <= 0) 207 | return -1; 208 | return TokPrec; 209 | } 210 | 211 | /// LogError* - These are little helper functions for error handling. 212 | std::unique_ptr LogError(const char *Str) { 213 | fprintf(stderr, "Error: %s\n", Str); 214 | return nullptr; 215 | } 216 | 217 | std::unique_ptr LogErrorP(const char *Str) { 218 | LogError(Str); 219 | return nullptr; 220 | } 221 | 222 | static std::unique_ptr ParseExpression(); 223 | 224 | /// numberexpr ::= number 225 | static std::unique_ptr ParseNumberExpr() { 226 | auto Result = std::make_unique(NumVal); 227 | getNextToken(); // consume the number 228 | return std::move(Result); 229 | } 230 | 231 | /// parenexpr ::= '(' expression ')' 232 | static std::unique_ptr ParseParenExpr() { 233 | getNextToken(); // eat (. 234 | auto V = ParseExpression(); 235 | if (!V) 236 | return nullptr; 237 | 238 | if (CurTok != ')') 239 | return LogError("expected ')'"); 240 | getNextToken(); // eat ). 241 | return V; 242 | } 243 | 244 | /// identifierexpr 245 | /// ::= identifier 246 | /// ::= identifier '(' expression* ')' 247 | static std::unique_ptr ParseIdentifierExpr() { 248 | std::string IdName = IdentifierStr; 249 | 250 | getNextToken(); // eat identifier. 251 | 252 | if (CurTok != '(') // Simple variable ref. 253 | return std::make_unique(IdName); 254 | 255 | // Call. 256 | getNextToken(); // eat ( 257 | std::vector> Args; 258 | if (CurTok != ')') { 259 | while (true) { 260 | if (auto Arg = ParseExpression()) 261 | Args.push_back(std::move(Arg)); 262 | else 263 | return nullptr; 264 | 265 | if (CurTok == ')') 266 | break; 267 | 268 | if (CurTok != ',') 269 | return LogError("Expected ')' or ',' in argument list"); 270 | getNextToken(); 271 | } 272 | } 273 | 274 | // Eat the ')'. 275 | getNextToken(); 276 | 277 | return std::make_unique(IdName, std::move(Args)); 278 | } 279 | 280 | /// primary 281 | /// ::= identifierexpr 282 | /// ::= numberexpr 283 | /// ::= parenexpr 284 | static std::unique_ptr ParsePrimary() { 285 | switch (CurTok) { 286 | default: 287 | return LogError("unknown token when expecting an expression"); 288 | case tok_identifier: 289 | return ParseIdentifierExpr(); 290 | case tok_number: 291 | return ParseNumberExpr(); 292 | case '(': 293 | return ParseParenExpr(); 294 | } 295 | } 296 | 297 | /// binoprhs 298 | /// ::= ('+' primary)* 299 | static std::unique_ptr ParseBinOpRHS(int ExprPrec, 300 | std::unique_ptr LHS) { 301 | // If this is a binop, find its precedence. 302 | while (true) { 303 | int TokPrec = GetTokPrecedence(); 304 | 305 | // If this is a binop that binds at least as tightly as the current binop, 306 | // consume it, otherwise we are done. 307 | if (TokPrec < ExprPrec) 308 | return LHS; 309 | 310 | // Okay, we know this is a binop. 311 | int BinOp = CurTok; 312 | getNextToken(); // eat binop 313 | 314 | // Parse the primary expression after the binary operator. 315 | auto RHS = ParsePrimary(); 316 | if (!RHS) 317 | return nullptr; 318 | 319 | // If BinOp binds less tightly with RHS than the operator after RHS, let 320 | // the pending operator take RHS as its LHS. 321 | int NextPrec = GetTokPrecedence(); 322 | if (TokPrec < NextPrec) { 323 | RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS)); 324 | if (!RHS) 325 | return nullptr; 326 | } 327 | 328 | // Merge LHS/RHS. 329 | LHS = 330 | std::make_unique(BinOp, std::move(LHS), std::move(RHS)); 331 | } 332 | } 333 | 334 | /// expression 335 | /// ::= primary binoprhs 336 | /// 337 | static std::unique_ptr ParseExpression() { 338 | auto LHS = ParsePrimary(); 339 | if (!LHS) 340 | return nullptr; 341 | 342 | return ParseBinOpRHS(0, std::move(LHS)); 343 | } 344 | 345 | /// prototype 346 | /// ::= id '(' id* ')' 347 | static std::unique_ptr ParsePrototype() { 348 | if (CurTok != tok_identifier) 349 | return LogErrorP("Expected function name in prototype"); 350 | 351 | std::string FnName = IdentifierStr; 352 | getNextToken(); 353 | 354 | if (CurTok != '(') 355 | return LogErrorP("Expected '(' in prototype"); 356 | 357 | std::vector ArgNames; 358 | while (getNextToken() == tok_identifier) 359 | ArgNames.push_back(IdentifierStr); 360 | if (CurTok != ')') 361 | return LogErrorP("Expected ')' in prototype"); 362 | 363 | // success. 364 | getNextToken(); // eat ')'. 365 | 366 | return std::make_unique(FnName, std::move(ArgNames)); 367 | } 368 | 369 | /// definition ::= 'def' prototype expression 370 | static std::unique_ptr ParseDefinition() { 371 | getNextToken(); // eat def. 372 | auto Proto = ParsePrototype(); 373 | if (!Proto) 374 | return nullptr; 375 | 376 | if (auto E = ParseExpression()) 377 | return std::make_unique(std::move(Proto), std::move(E)); 378 | return nullptr; 379 | } 380 | 381 | /// toplevelexpr ::= expression 382 | static std::unique_ptr ParseTopLevelExpr() { 383 | if (auto E = ParseExpression()) { 384 | // Make an anonymous proto. 385 | auto Proto = std::make_unique("__anon_expr", 386 | std::vector()); 387 | return std::make_unique(std::move(Proto), std::move(E)); 388 | } 389 | return nullptr; 390 | } 391 | 392 | /// external ::= 'extern' prototype 393 | static std::unique_ptr ParseExtern() { 394 | getNextToken(); // eat extern. 395 | return ParsePrototype(); 396 | } 397 | 398 | //===----------------------------------------------------------------------===// 399 | // Code Generation 400 | //===----------------------------------------------------------------------===// 401 | 402 | static LLVMContext TheContext; 403 | static IRBuilder<> Builder(TheContext); 404 | static std::unique_ptr TheModule; 405 | static std::map NamedValues; 406 | 407 | Value *LogErrorV(const char *Str) { 408 | LogError(Str); 409 | return nullptr; 410 | } 411 | 412 | Value *NumberExprAST::codegen() { 413 | return ConstantFP::get(TheContext, APFloat(Val)); 414 | } 415 | 416 | Value *VariableExprAST::codegen() { 417 | // Look this variable up in the function. 418 | Value *V = NamedValues[Name]; 419 | if (!V) 420 | return LogErrorV("Unknown variable name"); 421 | return V; 422 | } 423 | 424 | Value *BinaryExprAST::codegen() { 425 | Value *L = LHS->codegen(); 426 | Value *R = RHS->codegen(); 427 | if (!L || !R) 428 | return nullptr; 429 | 430 | switch (Op) { 431 | case '+': 432 | return Builder.CreateFAdd(L, R, "addtmp"); 433 | case '-': 434 | return Builder.CreateFSub(L, R, "subtmp"); 435 | case '*': 436 | return Builder.CreateFMul(L, R, "multmp"); 437 | case '<': 438 | L = Builder.CreateFCmpULT(L, R, "cmptmp"); 439 | // Convert bool 0/1 to double 0.0 or 1.0 440 | return Builder.CreateUIToFP(L, Type::getDoubleTy(TheContext), "booltmp"); 441 | default: 442 | return LogErrorV("invalid binary operator"); 443 | } 444 | } 445 | 446 | Value *CallExprAST::codegen() { 447 | // Look up the name in the global module table. 448 | Function *CalleeF = TheModule->getFunction(Callee); 449 | if (!CalleeF) 450 | return LogErrorV("Unknown function referenced"); 451 | 452 | // If argument mismatch error. 453 | if (CalleeF->arg_size() != Args.size()) 454 | return LogErrorV("Incorrect # arguments passed"); 455 | 456 | std::vector ArgsV; 457 | for (unsigned i = 0, e = Args.size(); i != e; ++i) { 458 | ArgsV.push_back(Args[i]->codegen()); 459 | if (!ArgsV.back()) 460 | return nullptr; 461 | } 462 | 463 | return Builder.CreateCall(CalleeF, ArgsV, "calltmp"); 464 | } 465 | 466 | Function *PrototypeAST::codegen() { 467 | // Make the function type: double(double,double) etc. 468 | std::vector Doubles(Args.size(), Type::getDoubleTy(TheContext)); 469 | FunctionType *FT = 470 | FunctionType::get(Type::getDoubleTy(TheContext), Doubles, false); 471 | 472 | Function *F = 473 | Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get()); 474 | 475 | // Set names for all arguments. 476 | unsigned Idx = 0; 477 | for (auto &Arg : F->args()) 478 | Arg.setName(Args[Idx++]); 479 | 480 | return F; 481 | } 482 | 483 | Function *FunctionAST::codegen() { 484 | // First, check for an existing function from a previous 'extern' declaration. 485 | Function *TheFunction = TheModule->getFunction(Proto->getName()); 486 | 487 | if (!TheFunction) 488 | TheFunction = Proto->codegen(); 489 | 490 | if (!TheFunction) 491 | return nullptr; 492 | 493 | // Create a new basic block to start insertion into. 494 | BasicBlock *BB = BasicBlock::Create(TheContext, "entry", TheFunction); 495 | Builder.SetInsertPoint(BB); 496 | 497 | // Record the function arguments in the NamedValues map. 498 | NamedValues.clear(); 499 | for (auto &Arg : TheFunction->args()) 500 | NamedValues[std::string(Arg.getName())] = &Arg; 501 | 502 | if (Value *RetVal = Body->codegen()) { 503 | // Finish off the function. 504 | Builder.CreateRet(RetVal); 505 | 506 | // Validate the generated code, checking for consistency. 507 | verifyFunction(*TheFunction); 508 | 509 | return TheFunction; 510 | } 511 | 512 | // Error reading body, remove function. 513 | TheFunction->eraseFromParent(); 514 | return nullptr; 515 | } 516 | 517 | //===----------------------------------------------------------------------===// 518 | // Top-Level parsing and JIT Driver 519 | //===----------------------------------------------------------------------===// 520 | 521 | static void HandleDefinition() { 522 | if (auto FnAST = ParseDefinition()) { 523 | if (auto *FnIR = FnAST->codegen()) { 524 | fprintf(stderr, "Read function definition:"); 525 | FnIR->print(errs()); 526 | fprintf(stderr, "\n"); 527 | } 528 | } else { 529 | // Skip token for error recovery. 530 | getNextToken(); 531 | } 532 | } 533 | 534 | static void HandleExtern() { 535 | if (auto ProtoAST = ParseExtern()) { 536 | if (auto *FnIR = ProtoAST->codegen()) { 537 | fprintf(stderr, "Read extern: "); 538 | FnIR->print(errs()); 539 | fprintf(stderr, "\n"); 540 | } 541 | } else { 542 | // Skip token for error recovery. 543 | getNextToken(); 544 | } 545 | } 546 | 547 | static void HandleTopLevelExpression() { 548 | // Evaluate a top-level expression into an anonymous function. 549 | if (auto FnAST = ParseTopLevelExpr()) { 550 | if (auto *FnIR = FnAST->codegen()) { 551 | fprintf(stderr, "Read top-level expression:"); 552 | FnIR->print(errs()); 553 | fprintf(stderr, "\n"); 554 | } 555 | } else { 556 | // Skip token for error recovery. 557 | getNextToken(); 558 | } 559 | } 560 | 561 | /// top ::= definition | external | expression | ';' 562 | static void MainLoop() { 563 | while (true) { 564 | fprintf(stderr, "ready> "); 565 | switch (CurTok) { 566 | case tok_eof: 567 | return; 568 | case ';': // ignore top-level semicolons. 569 | getNextToken(); 570 | break; 571 | case tok_def: 572 | HandleDefinition(); 573 | break; 574 | case tok_extern: 575 | HandleExtern(); 576 | break; 577 | default: 578 | HandleTopLevelExpression(); 579 | break; 580 | } 581 | } 582 | } 583 | 584 | //===----------------------------------------------------------------------===// 585 | // Main driver code. 586 | //===----------------------------------------------------------------------===// 587 | 588 | int main() { 589 | // Install standard binary operators. 590 | // 1 is lowest precedence. 591 | BinopPrecedence['<'] = 10; 592 | BinopPrecedence['+'] = 20; 593 | BinopPrecedence['-'] = 20; 594 | BinopPrecedence['*'] = 40; // highest. 595 | 596 | // Prime the first token. 597 | fprintf(stderr, "ready> "); 598 | getNextToken(); 599 | 600 | // Make the module, which holds all the code. 601 | TheModule = std::make_unique("my cool jit", TheContext); 602 | 603 | // Run the main "interpreter loop" now. 604 | MainLoop(); 605 | 606 | // Print out all of the generated code. 607 | TheModule->print(errs(), nullptr); 608 | 609 | return 0; 610 | } 611 | -------------------------------------------------------------------------------- /clion-project/kaleidoscope/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /clion-project/kaleidoscope/.idea/kaleidoscope.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /clion-project/kaleidoscope/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /clion-project/kaleidoscope/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /clion-project/kaleidoscope/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /clion-project/kaleidoscope/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |