vlambda博客
学习文章列表

使用LLVM实现自定义语言的编译器(源代码->可执行文件)!

文章使用的LLVM版本:LLVM 9.0.1

代码:https://github.com/Kingtous/kaleidoscope-llvm-compiler

参考教程:https://github.com/zy445566/llvm-guide-zh

LLVM 是 Low Level Virtual Machine (低级虚拟机)的简称,这个库提供了与编译器相关的支持,可以作为多种语言编译器的后台来使用。

LLVM环境配置

此处使用cmake完成LLVM环境的配置

cmake_minimum_required(VERSION 3.15)
project(llvm_kaleidoscope)

set(CMAKE_CXX_STANDARD 11)

find_package(LLVM REQUIRED CONFIG)

message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Using defs in: ${LLVM_DEFINITIONS}")

# import LLVM
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

add_executable(llvm_kaleidoscope main.cpp Lexer.cpp Lexer.h ExprAST.cpp ExprAST.h Parser.cpp Parser.h global.h file_reader_wrapper.h err_helper.h err_helper.cpp global.cpp FileReader.cpp FileReader.h)

# Link against LLVM libraries
target_link_libraries(llvm_kaleidoscope LLVM)

将LLVM导入项目

导包

#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Scalar/GVN.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"

编写自己的词法分析器

此处只是大致流程,详细请查看源码

定义token

/// Token类型
enum Token {
   tok_eof = -1// EOF
   tok_def = -2// 函数定义
   tok_extern = -3//调用的标准库函数
   tok_identifier = -4//标识符
   tok_number = -5 //数字
};

定义文件读取器

#ifndef LLVM_KALEIDOSCOPE_FILEREADER_H
#define LLVM_KALEIDOSCOPE_FILEREADER_H

#include <string>

#include "file_reader_wrapper.h"

class FileReader : public FileReaderWrapper {

   std::string content;
   unsigned int index = 0;

public:
   explicit FileReader(const std::string &path);
// 获取下一个字符
   char getchar() override;
// 查看当前字符
   char seek() override;
   unsigned long content_length;
};


#endif //LLVM_KALEIDOSCOPE_FILEREADER_H

定义Lexer词法分析器

  • 仅为参考,可以有多种实现方式

  • 实现代码见仓库

class Lexer {
public:
   std::string identifierStr//如果是tok_identifier就传入
   double NumVal = INIT_NUM//如果是tok_number则表示数字
   int currToken//当前的token

   /// 当前的Token
   FileReader reader;
   /// lexer构造函数:传入代码
   /// params: rawStr 代码全文
   explicit Lexer(FileReader fileReader) : reader(std::move(fileReader)) {}
   /// 获取下一个token
   int getNextToken();

private:
   int _getNextToken();
   char getchar();
   char seek();

};

定义AST语法树

实现内容:

  • 基础结点

  • 数值结点

  • 变量结点

  • 二元操作结点

  • 函数

    • 函数描述结点

    • 函数实现结点

注意:

codegen用于生产LLVM IR代码

/// 基结点
class ExprAST {
public:
   virtual ~ExprAST() = default;

   virtual llvm::Value *codegen() = 0;
};

/// 数值语法结点
class NumberExprAST : public ExprAST {
   double Val;
public:
   explicit NumberExprAST(double val) : Val(val) {}

   /// codegen()方法表示为该AST节点发出IR及其依赖的所有内容,并且它们都返回一个LLVM Value对象。
   /// “Value”是用于表示 LLVM中的“ 静态单一分配(SSA)寄存器”或“SSA值”的类。
   /// SSA值的最独特之处在于它们的值是在相关指令执行时计算的,并且在指令重新执行之前(以及如果)它不会获得新值。
   /// 换句话说,没有办法“改变”SSA值。
   llvm::Value *codegen() override;
};

/// 变量结点
class VariableExprAST : public ExprAST {
   std::string Name;
public:
   explicit VariableExprAST(std::string &Name) : Name(Name) {}

   llvm::Value *codegen() override;
};

/// 二元操作结点
class BinaryExprAST : public ExprAST {
   // 操作符
   char Oper;
   std::unique_ptr<ExprAST> LEAREA;
public:
   explicit BinaryExprAST(char operstd::unique_ptr<ExprAST> LEAstd::unique_ptr<ExprAST> REA) : Oper(oper),
                                                                                                   LEA(std::move(LEA)),
                                                                                                   REA(std::move(
                                                                                                           REA)) {}

   llvm::Value *codegen() override;
};

/// 函数调用结点
class CallExprAST : public ExprAST {
   std::string callName;
   std::vector<std::unique_ptr<ExprAST>> args;

public:
   CallExprAST(std::string callNamestd::vector<std::unique_ptr<ExprAST>> args) : callName(std::move(callName)),
                                                                                   args(std::move(args)) {};

   llvm::Value *codegen() override;
};

/// 函数描述结点
/// 用于描述函数名字,参数名称,参数个数
class PrototypeAST : public ExprAST {
   std::string name;
   std::vector<std::string> args;

public:
   PrototypeAST(std::string namestd::vector<std::string> args) : name(std::move(name)), args(std::move(args)) {}

   llvm::Function *codegen() override;

   const std::string &getName() const;
};

/// 函数结点
class FunctionAST : public ExprAST {
   std::unique_ptr<PrototypeAST> Proto;
   std::unique_ptr<ExprAST> Body;
public:
   FunctionAST(std::unique_ptr<PrototypeAST> Protostd::unique_ptr<ExprAST> Body) : Proto(std::move(Proto)),
                                                                                     Body(std::move(Body)) {}

   llvm::Function *codegen() override;
};

定义语法分析器

要点:

  • 实现解析函数

    • 数字

    • 标识符

    • 函数定义

    • 表达式

  • 实现二元操作优先级比较

  • [可选]编写test()函数测试语法分析器

/// 语法分析器
class Parser {
public:
   Parser(Lexer lexer);

   // 词法分析器
   Lexer _lexer;

   // 解析数字
   std::unique_ptr<ExprAST> parseNumberExpr();

   // 解析标识符
   std::unique_ptr<ExprAST> parseIdentifierExpr();

   // 解析表达式
   std::unique_ptr<ExprAST> parseExpression();

   // 封装的处理方法
   std::unique_ptr<ExprAST> parsePrimary();

   // 处理带有括号的表达式
   std::unique_ptr<ExprAST> parseParentExpr();

   // 处理函数定义
   std::unique_ptr<PrototypeAST> parsePrototypeExpr();

   // 处理函数def
   std::unique_ptr<FunctionAST> parseDefinition();

   // 处理extern
   std::unique_ptr<PrototypeAST> parseExtern();

   // 测试函数:输入任意表达式进行测试
   std::unique_ptr<FunctionAST> parseTopLevelExpr();

   // 测试
   void test();

private:
   std::map<charint> binOpPriority;

   std::unique_ptr<ExprAST> parseBinOpRHS(int prioritystd::unique_ptr<ExprAST> &&uniquePtr);

   // 获取token优先级
   int getTokenPriority();

   void HandleDefinition();

   void HandleExtern();

   void HandleTopLevelExpression();
};

创建测试代码

  • 源文件内容("一句话函数")

def average(x y) (x+y)/2;
  • 编译器main函数

    • LLVM支持交叉编译,可以在tarrgetTriple里定义其他目标系统以生成其他架构的代码

    • // 生成目标代码
         InitializeAllTargetInfos();
         InitializeAllTargets();
         InitializeAllTargetMCs();
         InitializeAllAsmParsers();
         InitializeAllAsmPrinters();

         auto TargetTriple = sys::getDefaultTargetTriple();
         TheModule->setTargetTriple(TargetTriple);

         std::string Error;
         auto Target = TargetRegistry::lookupTarget(TargetTripleError);

         // Print an error and exit if we couldn't find the requested target.
         // This generally occurs if we've forgotten to initialise the
         // TargetRegistry or we have a bogus target triple.
         if (!Target) {
             errs() << Error;
             return 1;
        }

         auto CPU = "generic";
         auto Features = "";

         TargetOptions opt;
         auto RM = Optional<Reloc::Model>();
         auto TheTargetMachine =
                 Target->createTargetMachine(TargetTripleCPUFeaturesoptRM);

         TheModule->setDataLayout(TheTargetMachine->createDataLayout());

         auto Filename = "output.o";
         std::error_code EC;
         raw_fd_ostream dest(FilenameECsys::fs::F_None);

         if (EC) {
             errs() << "Could not open file: " << EC.message();
             return 1;
        }

         legacy::PassManager pass;
         auto FileType = TargetMachine::CGFT_ObjectFile;

         if (TheTargetMachine->addPassesToEmitFile(passdestnullptrFileType)) {
             errs() << "TheTargetMachine can't emit a file of this type";
             return 1;
        }

         pass.run(*TheModule);
         dest.flush();

         outs() << "Wrote " << Filename << "\n";


    • TheModule = llvm::make_unique<Module>("Kingtous' jit"TheContext);
         FileReader reader("/Users/kingtous/CLionProjects/llvm-kaleidoscope/test.kl");
         Lexer lexer(reader);
         Parser parser(lexer);
         parser.test();
    • 词法、语法分析

    • 生成目标代码

最终效果

为了能实现输出,此处使用C++,调用自定义语言中定义的average函数  (def average(x y) (x+y)/2;),算出平均值。先编译出.o文件与main.cpp进行链接。

main.cpp:

#include <iostream>

extern "C" {
   double average(double,double);
}

int main() {
   double a,b;
   std::cout<<"average number calculator"<<std::endl;
   std::cout<<"input a,b:";
   std::cin >> a >> b;
   std::cout << "average:" << average(a,b<< std::endl;
}

反编译结果:

编译指令:g++ main.cpp output.o -o main

运行效果: