【LLVM学习系列】(二)llvm新手教程(Toby Ho)P7-P11

简介

本章将向您展示如何将第二章构建的抽象语法树转换为 LLVM IR。这将为您介绍 LLVM 的一些工作原理,并演示其使用的简易性。构建词法分析器和解析器比生成 LLVM IR 代码要复杂得多。请注意:本章及以后的代码需要 LLVM 3.7 或更高版本。LLVM 3.6 及之前的版本不兼容。另外,请注意您需要使用与您的 LLVM 发行版匹配的本教程版本:如果您正在使用官方 LLVM 发行版,请使用与您的发行版或 llvm.org 发布页面上提供的文档版本相匹配的教程版本。
本文仍然沿用主页的教程。

3.1 Static single assignment form

静态单赋值SSA是LLVM中的核心存在,含义为 Every var can only be assigned once.
SSA的主要用处在于它同时简化和改进了各种编译器优化的结果,通过简化变量的属性。例如,考虑下面的代码片段:

y := 1
y := 2
x := y

可以看出第一个赋值是不必要的,并且第三行中使用的 y 的值来自于第二次赋值。程序必须执行到达定义分析来确定这一点。但如果程序处于 SSA 形式,这两个都是immediate的:

y1 := 1
y2 := 2
x1 := y2

使用 SSA(静态单赋值)技术启用或强化的编译器优化算法包括:
常量传播 - 将运行时的计算转换为编译时,例如将指令 a=3*4+5; 视为 a=17;
值范围传播 - 预先计算计算可能的范围,允许提前创建分支预测
稀疏条件常量传播 - 对一些值进行范围检查,允许测试预测最可能的分支
死代码消除 - 删除对结果没有影响的代码
全局值编号 - 替换产生相同结果的重复计算
部分冗余消除 - 删除先前在程序某些分支中执行的重复计算
强度降低 - 用较便宜但等效的操作替换昂贵的操作,例如用左移(乘法)或右移(除法)替换整数乘法或除法的指数2
寄存器分配 - 优化有限数量的机器寄存器如何用于计算

3.2 CodeGen

为了生成 LLVM IR,我们需要一些简单的设置来开始。首先,我们在每个 AST 类中定义虚拟代码生成(codegen)方法:

/// ExprAST - Base class for all expression nodes.
class ExprAST {
public:
  virtual ~ExprAST() = default;
  //基类中使用 = 0 表示纯虚函数。纯虚函数是在基类中声明但没有提供实现的虚函数。派生类必须提供实现以使基类变得可实例化
  virtual Value *codegen() = 0;
};

// NumberExprAST - for numeric literals like "1.0"
class NumberExprAST: public ExprAST{
    double Val;

public:
    NumberExprAST(double Val) : Val(Val){}
    Value *codegen() override;
};

codegen() 方法用于为该AST节点及其所有依赖项发出IR,并且它们都返回一个LLVM Value对象。“Value”是LLVM中用于表示“静态单赋值(SSA)寄存器”或“SSA值”的类。SSA值最显著的特点是,它们的值是随着相关指令的执行而计算的,并且直到(如果)指令重新执行,它们才会获得新值。换句话说,没有办法“改变”SSA值。有关更多信息,请阅读关于Static Single Assignment的资料-一旦理解了这些概念,就会觉得它们非常自然。

请注意,与其向ExprAST类层次结构添加虚拟方法,使用访问者模式或其他模型也可能是有意义的。再次强调,本教程不会详细讨论良好的软件工程实践:对于我们的目的,添加虚拟方法是最简单的。

static unique_ptr<LLVMContext> TheContext;
static unique_ptr<IRBuilder<>> Builder(TheContext);
static unique_ptr<Module> TheModule;
static map<string, Value *> NamedValues;

Value *LogErrorV(const char *Str){
    LogError(Str);
    return nullptr;
}

静态变量将在代码生成过程中使用。TheContext 是一个不透明对象,拥有许多核心的 LLVM 数据结构,例如类型和常量值表。我们不需要详细了解它,我们只需要一个单一实例来传递给需要它的 API。

Builder 对象是一个辅助对象,可以轻松生成 LLVM 指令。IRBuilder 类模板的实例跟踪当前插入指令的位置,并具有创建新指令的方法。

TheModule 是一个包含函数和全局变量的 LLVM 结构。在许多方面,它是 LLVM IR 用来包含代码的顶级结构。它将拥有我们生成的所有 IR 的内存,这就是为什么 codegen() 方法返回一个原始的 Value*,而不是一个 unique_ptr。

NamedValues 映射跟踪当前作用域中定义的值以及它们的 LLVM 表示形式。(换句话说,它是代码的符号表)。在这个 Kaleidoscope 的形式中,唯一可以引用的是函数参数。因此,在为函数体生成代码时,函数参数将在这个映射中。

有了这些基础,我们可以开始讨论如何为每个表达式生成代码。请注意,这假设 Builder 已经设置好,可以将代码生成到某个地方。暂时我们假设已经完成了这个设置,并且只是使用它来发出代码。

3.2.1 Expression Code Generation

// NumberExprAST - for numeric literals like "1.0"
class NumberExprAST: public ExprAST{
    double Val;

public:
    NumberExprAST(double Val) : Val(Val){}
    Value *codegen() override;
};

Value *NumberExprAST::codegen(){
    return ConstantFP::get(*TheContext, APFloat(Val));
}


class VariableExprAST: public ExprAST{
    std::string Name;

public: 
    VariableExprAST(const std::string &Name): Name(Name){}

    Value *codegen() override;
};

Value *VariableExprAST::codegen(){
    // look this variable up in the function
    Value *V = NamedValues[Name];
    if (!V)
        LogErrorV("Unknown variable name");

    return V;
}


class BinaryExprAST: public ExprAST{ // 2+3
    char Op; // + - * \ < > 
    std::unique_ptr<ExprAST> LHS, RHS;

// smart pointer: c++ doesn't have garbage collector
public:
    BinaryExprAST(char Op, std::unique_ptr<ExprAST> LHS, std::unique_ptr<ExprAST> RHS):
    Op(Op), LHS(move(LHS)), RHS(move(RHS)){}

    Value *codegen() override;
};


Value *BinaryExprAST::codegen(){
    Value *L = LHS->codegen();
    Value *R = RHS->codegen();
    if (!L || !R)
        return nullptr;

    switch(Op){
    case '+':
        return Builder->CreateFAdd(L, R, "addtmp");
    case '-':
        return Builder->CreateFAdd(L, R, "subtmp");
    case '*':
        return Builder->CreateFMul(L, R, "multmp");
    case '<':
        L = Builder->CreateFCmpULT(L, R, "cmptmp");
        return Builder->CreateUIToFP(L, Type::getDoubleTy(TheContext), "booltmp");

    default:
        return LogErrorV("invalid binary operator");
    }
}

class CallExprAST: public ExprAST{
    std::string Callee;
    std::vector<std::unique_ptr<ExprAST>> Args;

public:
    CallExprAST(const std::string &Callee, std::vector<std::unique_ptr<ExprAST>> Args):
    Callee(Callee), Args(move(Args)){}

    Value *codegen() override;
};

Value *CallExprAST::codegen(){
    // look up the name in the global module table
    Function *CalleeF = TheModule->getFunction(Callee);
    if (!CalleeF)
        return LogErrorV("Unknown function referenced");

    // if argument mismatch error
    if (CalleeF->arg_size() != Args.size())
        return LogErrorV("Incorrect # arguments passed");

    std::vector<Value *> ArgsV;
    for (unsigned i=0, e=Args.size();i!=e;++i){
        ArgsV.push_back(Args[i]->codegen());
        if (!ArgsV.back()) // the last element
            return nullptr;
    }

    return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
}

3.2.2 Function Code Generation


Function *PrototypeAST::codegen(){
    // make the function type: double(double, double) etc.
    std::vector<Type*> Doubles(Args.size(),Type::getDoubleTy(*TheContext));

    FunctionType *FT = FunctionType::get(Type::getDoubleTy(*TheContext), Doubles, false);

    Function *F = Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get());


    //Set names for all arguments
    unsigned Idx = 0;
    for (auto &Arg: F->args())
        Arg.setName(Args[Idx++]);

    return F;
}

这段代码凝练而功能强大。首先要注意的是,这个函数返回一个“Function*”,而不是一个“Value*”。因为“prototype”实际上描述的是一个函数的外部接口(而不是表达式计算得到的值),因此返回与其对应的LLVM函数的指针是合理的。

调用FunctionType::get创建了应用于给定原型的FunctionType。由于Kaleidoscope中的所有函数参数都是double类型,因此第一行创建了一个由“N”个LLVM double类型组成的向量。然后使用FunctionType::get方法创建了一个函数类型,该类型以“N”个double类型的参数作为参数,返回一个double类型的结果,并且不是vararg(false参数指示此情况)。请注意,LLVM中的类型与常量一样是唯一的,因此您不需要“new”一个类型,而是“get”它。

上面的最后一行实际上创建了与Prototype相对应的IR函数。这指示了要使用的类型、链接和名称,以及要插入到哪个模块中。“external linkage”意味着该函数可以在当前模块之外定义,和/或者可被模块外的函数调用。传入的名称是用户指定的名称:由于指定了“TheModule”,因此此名称将在“TheModule”的符号表中注册。


Function *FunctionAST::codegen(){
    //first, check for an existing function from a previous 'extern' declaration
    Function *TheFunction = TheModule->getFunction(Proto->getName());

    if (!TheFunction)
        TheFunction = Proto->codegen();

    if (!TheFunction)
        return nullptr;

    if (!TheFunction->empty())
        return (Function*)LogErrorV("Function cannot be redefined");


    // create a new bb to start insertion into
    BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction);
    Builder->SetInsertPoint(BB);

    // record the function arguments in the NamedValues map
    NamedValues.clear();
    for (auto &Arg: TheFunction->args())
        NamedValues[std::string(Arg.getName())] = &Arg;

    Value *RetVal = Body->codegen();
    if (RetVal){
        //finish off the function
        Builder->CreateRet(RetVal);

        //velidate the generated code, checking for consistency
        verifyFunction(*TheFunction);

        return TheFunction;
    }

    //error reading body ,remove funciton
    TheFunction->eraseFromParent();
    return nullptr;
}

对于函数定义,我们首先搜索TheModule的符号表,以查找是否已经使用“extern”语句创建了此函数的现有版本。如果Module::getFunction返回null,则表示不存在先前的版本,因此我们将从Prototype开始生成一个。在任何情况下,我们都希望在开始之前断言函数是空的(即尚未具有body)。

现在我们来到了设置Builder的地方。第一行创建了一个新的基本块(命名为“entry”),它被插入到TheFunction中。第二行告诉构建器新指令应该插入到新基本块的末尾。LLVM中的基本块是定义控制流图的函数的重要部分。由于我们没有任何控制流,所以在这一点上,我们的函数只包含一个块。我们将在第五章中解决这个问题 😃.

接下来,我们将函数参数添加到NamedValues映射中(首先清除它),以便它们对VariableExprAST节点可访问。

一旦插入点设置好并且NamedValues映射填充好,我们就调用函数的根表达式的codegen()方法。如果没有错误发生,这将生成代码来计算表达式并将其放入入口块,并返回计算得到的值。假设没有错误,我们然后创建一个LLVM ret指令,这样函数就完成了。构建函数后,我们调用verifyFunction,这是LLVM提供的函数。该函数对生成的代码执行各种一致性检查,以确定我们的编译器是否正确执行了所有操作。使用这个函数很重要:它可以捕获到很多错误。一旦函数完成并验证通过,我们就返回它。

3.2.3 top-level parsing and jit driver

static void InitializeModule(){
    //Open a new context and module
    TheContext = std::make_unique<LLVMContext>();
    TheModule = std::make_unique<Module>("my cool jit", *TheContext);

    //create a new builder for the module
    Builder = std::make_unique<IRBuilder<>>(*TheContext);
}


static void HandleDefinition(){
    if(auto FnAST = ParseDefinition()){
        if (auto *FnIR = FnAST->codegen()){
            fprintf(stderr, "Read function definition: ");
            FnIR->print(errs());
            fprintf(stderr, "\n");
        }
    }
    else //skip token for error recovery
        getNextToken();
}

static void HandleExtern(){
    if (auto ProtoAST = ParseExtern()){
        if (auto *FnIR = ProtoAST->codegen()){
            fprintf(stderr, "Read extern: ");
            FnIR->print(errs());
            fprintf(stderr, "\n");
        }
    }
    else //skip token for error recovery
        getNextToken();
}

static void  HandleTopLevelExpression(){
    // evaluate a top-level expression into an anonymous function
    if (auto FnAST = ParseTopLevelExpr()){
        if (auto *FnIR = FnAST->codegen()){
            fprintf(stderr, "Read top-level expression: \n");
            FnIR->print(errs());
            fprintf(stderr, "\n");

            // remove token for error recovery
            FnIR->eraseFromParent();
        }
    }
    else
        getNextToken();
}

// the driver
static void MainLoop(){
    while(true){
        fprintf(stderr, "read> ");
        switch(CurTok){
        case tok_eof:
            return;
        case ';': // ignore top-level semicolons
            getNextToken();
            break;
        case tok_def:
            HandleDefinition();
            break;
        case tok_extern:
            HandleExtern();
            break;
        default:
            HandleTopLevelExpression();
            break;
        }
    }
}

Debug & Run

测试函数:


int main(){
    fprintf(stderr, "ready> ");

    InitializeModule();
    getNextToken();
    MainLoop();

    return 0;
}

下载官网llvm10的预编译包,基于此进行扩展。

编译命令:

export PATH=/home//llvm-10.0/bin:$PATH
clang++ -g -O3 my-lang.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core` -o my-lang.bin

运行结果:
在这里插入图片描述
在这里插入图片描述

gdb tips

GDB GUI 是一系列工具的统称,它们提供了图形界面来辅助使用 GDB(GNU Debugger),从而使得调试过程更加直观和方便。这些工具通过图形化的方式展示源代码、变量、断点、线程等调试信息,使得开发者能够更加高效地进行代码调试。

vscode gdb

安装:sudo apt-get install lldb
使用:lldb ./my-lang.bin or gdb ./my-lang.bin

使用vscode 进行可视化gdb要更加方便快捷,配置步骤见另一篇博客

full code

#include "llvm/ADT/APFloat.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/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Verifier.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <iostream>

using namespace llvm;

enum Token{
    tok_eof=-1,

    tok_def=-2,
    tok_extern=-3,

    tok_identifier=-4,
    tok_number=-5
};

static std::unique_ptr<LLVMContext> TheContext;
static std::unique_ptr<IRBuilder<>> Builder;
static std::unique_ptr<Module> TheModule;
static std::map<std::string, Value *> NamedValues;

// 在编程语言的词法分析器(lexer)中,标识符通常是指用来标识变量、函数、类、模块等命名实体的字符序列。
static std::string IdentifierStr="";  // filled in if tok_identifier
static double NumVal;        // filled in if tok_number

// gettok
static int gettok(){
    static int LastChar = ' ';
    
    // skip space
    while(isspace(LastChar)){
        LastChar = getchar();
    }

    // get identifier
    if (isalpha(LastChar)){ // identifier: [a-zA-z][a-zA-Z0-9]*
        IdentifierStr = LastChar;
        while (isalnum(LastChar=getchar()))
            IdentifierStr += LastChar;

        if (IdentifierStr == "def")
            return tok_def;

        if (IdentifierStr == "extern")
            return tok_extern;

        return tok_identifier;
    }

    // get number
    if (isdigit(LastChar) || LastChar == '.'){ // Number: [0-9.]+
        std::string NumStr;
        do{
            NumStr += LastChar;
            LastChar = getchar();
        }while (isdigit(LastChar) || LastChar == '.');

        // 这行代码的功能是将一个字符串转换为双精度浮点数
        // .c_str() 方法将 NumStr 转换为以空字符结尾的 C 风格字符串(即以 const char* 的形式返回字符串的指针)
        // strtod() 是一个 C 标准库函数,用于将字符串转换为双精度浮点数。两个参数:第一个参数是要转换的字符串的指针,第二个参数是一个指向字符指针的指针,用于存储第一个无法转换的字符的地址。在这里第二个参数为 0,表示不需要获取无法转换的字符的地址。
        NumVal = strtod(NumStr.c_str(), 0);
        return tok_number;
    }

    // fill comments
    if(LastChar == '#'){
        // comments until end of line
        do
            LastChar = getchar();
        while(LastChar !=EOF && LastChar != '\n' && LastChar != '\r');

        if (LastChar != EOF)
        return gettok();
    }

    if (LastChar == EOF)
        return tok_eof;

    // otherwise, just return the character as its ascii value
    int ThisChar = LastChar;
    LastChar = getchar();
    return ThisChar;
}


// 2.2. The Abstract Syntax Tree (AST)

// ExprAST- base class
class ExprAST{
public:
    virtual ~ExprAST() = default;
    //基类中使用 = 0 表示纯虚函数。纯虚函数是在基类中声明但没有提供实现的虚函数。派生类必须提供实现以使基类变得可实例化
    virtual Value *codegen() = 0;
};

// NumberExprAST - for numeric literals like "1.0"
class NumberExprAST: public ExprAST{
    double Val;

public:
    NumberExprAST(double Val) : Val(Val){}
    Value *codegen() override;
};



class VariableExprAST: public ExprAST{
    std::string Name;

public: 
    VariableExprAST(const std::string &Name): Name(Name){}

    Value *codegen() override;
};


class BinaryExprAST: public ExprAST{ // 2+3
    char Op; // + - * \ < > 
    std::unique_ptr<ExprAST> LHS, RHS;

// smart pointer: c++ doesn't have garbage collector
public:
    BinaryExprAST(char Op, std::unique_ptr<ExprAST> LHS, std::unique_ptr<ExprAST> RHS):
    Op(Op), LHS(move(LHS)), RHS(move(RHS)){}

    Value *codegen() override;
};


class CallExprAST: public ExprAST{
    std::string Callee;
    std::vector<std::unique_ptr<ExprAST>> Args;

public:
    CallExprAST(const std::string &Callee, std::vector<std::unique_ptr<ExprAST>> Args):
    Callee(Callee), Args(move(Args)){}

    Value *codegen() override;
};


//Function Code Generation

class PrototypeAST{ // func(string xxx,)
    std::string Name;
    std::vector<std::string> Args;

public: 
    PrototypeAST(const std::string&Name, std::vector<std::string> Args):
    Name(Name), Args(move(Args)){}

    //getName() 后面加上 const 关键字的原因是为了表明这个成员函数是一个常量成员函数。在C++中,常量成员函数是指那些在函数体内不会修改类的成员变量的成员函数
    const std::string &getName() const{
        return Name;
    }

    Function *codegen();
};

class FunctionAST{ // def func(string xxx,){}
    std::unique_ptr<PrototypeAST> Proto;
    std::unique_ptr<ExprAST> Body;

public:
    FunctionAST(std::unique_ptr<PrototypeAST> Proto, std::unique_ptr<ExprAST> Body):
    Proto(move(Proto)), Body(move(Body)){}

    Function *codegen();
};

// Basic Expression Parsing

// LogError
std::unique_ptr<ExprAST> LogError(const char *Str){
    fprintf(stderr, "Error: %s\n", Str);
    return nullptr;
}

std::unique_ptr<PrototypeAST> LogErrorP(const char *Str){
    LogError(Str);
    return nullptr;
}

// CurTok/getNextToken - 提供一个简单的token缓冲区。CurTok是解析器当前正在查看的令牌。
// getNextToken从词法分析器中读取另一个令牌,并使用其结果更新CurTok。

static int CurTok;
static int getNextToken(){
    return CurTok = gettok();
}

static std::unique_ptr<ExprAST> ParseExpression();
// 这个例子吃掉了与产生式相对应的所有令牌,并返回词法分析器缓冲区中的下一个令牌
// 这是递归下降解析器的一种相当标准的做法。
// numberexpr ::=number
static std::unique_ptr<ExprAST> ParseNumberExpr(){
    auto Result  = std::make_unique<NumberExprAST>(NumVal);
    getNextToken(); // consume the number
    return move(Result);
}

// 括号运算符的定义如下:
// parenexpr ::= '(' expression')'
static std::unique_ptr<ExprAST> ParseParenExpr(){
    getNextToken(); // eat (
    auto V = ParseExpression();

    if (!V)
        return nullptr;
    if (CurTok != ')')
        return LogError("expected ')");

    getNextToken(); // eat ')'
    return V;
}

// 用于处理变量引用和函数调用
// IdentifierExpr 
// :: identifier
// :: identifier '(' expression* ')'
static std::unique_ptr<ExprAST> ParseIdentifierExpr() {
    std::string IdName = IdentifierStr;

    getNextToken(); // eat identifier

    if (CurTok == '('){ // call
        getNextToken(); // eat (
        std::vector<std::unique_ptr<ExprAST>> Args;
        while(true){
            auto Arg = ParseExpression();
            if (Arg)
                Args.push_back(move(Arg));
            else
                return nullptr;

            if (CurTok == ')'){
                getNextToken(); // eat )
                break;
            }else if (CurTok == ','){
                getNextToken(); // eat ,
                continue;
            }else{
                return LogError("Expected ')' or ',' in argument list");
            }
        }

        return std::make_unique<CallExprAST>(IdName, move(Args));

    }else{ // simple variable ref
        return std::make_unique<VariableExprAST>(IdName);
    }

}

// 已经把所有简单表达式解析逻辑放到了一起,可以定义一个辅助函数将它们封装到一个入口点中
// primary 

static std::unique_ptr<ExprAST> ParsePrimary(){
    switch(CurTok){
    default:
        return LogError("unknown token when expecting an expression");
    case tok_identifier:
        return ParseIdentifierExpr();
    case tok_number:
        return ParseNumberExpr();
    case '(':
        return ParseParenExpr();
    }
}

//  Binary Expression Parsing
// 2+2  I +( 2*3 )

static int GetTokPrecedence(){
    switch(CurTok){
        case '<':
        case '>':
            return 10;
        case '+':
        case '-':
            return 20;
        case '*':
        case '/':
            return 40;
        default:
            return -1;
    }
}


// binoprhs
// :: = ( + primary  ) *
static std::unique_ptr<ExprAST> ParseBinOpRHS(
    int ExprPrec, std::unique_ptr<ExprAST> LHS){
        while(true){
            int TokPrec = GetTokPrecedence(); // binop prec

            if (TokPrec < ExprPrec){
                return LHS;
            }else {
                int BinOp = CurTok;
                getNextToken(); // eat binop
                auto RHS = ParsePrimary();
                if (RHS){
                    int NextPrec = GetTokPrecedence();
                    if(TokPrec < NextPrec){ // + VS *
                        // ExprPrec is (TokPrec + RHS_Prec)
                        RHS = ParseBinOpRHS(TokPrec+1, move(RHS));
                        if (!RHS)
                            return nullptr;
                    }
                    LHS = std::make_unique<BinaryExprAST>(BinOp, move(LHS), move(RHS));
                } else {
                    return nullptr;
                }
            }
        }
}
 


 
// expression
static std::unique_ptr<ExprAST> ParseExpression(){
    auto LHS = ParsePrimary();
    if (LHS)
        return ParseBinOpRHS(0, move(LHS));
    else
        return nullptr;
}

// parse the rest

//prototype
// foobar(n m)
static std::unique_ptr<PrototypeAST> ParsePrototype(){
    if (CurTok != tok_identifier)
        return LogErrorP("Expected function name in prototype");

    std::string FnName = IdentifierStr;
    getNextToken();

    if (CurTok != '(')
        return LogErrorP("Expected '(' in prototype");

    // read args
    std::vector<std::string> ArgNames;
    while (getNextToken() == tok_identifier)
        ArgNames.push_back(IdentifierStr);
    if (CurTok != ')')
        return LogErrorP("Expected ')' in prototype");

    getNextToken();
    return std::make_unique<PrototypeAST>(FnName, move(ArgNames));
}

// parse definition, body
static std::unique_ptr<FunctionAST> ParseDefinition(){
    getNextToken();
    auto Proto = ParsePrototype();
    if (!Proto)
        return nullptr;
    
    if (auto E = ParseExpression())
        return std::make_unique<FunctionAST>(move(Proto), move(E));

    return nullptr;
}

// extern prototype
static std::unique_ptr<PrototypeAST> ParseExtern(){
    getNextToken(); // eat extern
    return ParsePrototype();
}

//topevelexpr expression
static std::unique_ptr<FunctionAST> ParseTopLevelExpr(){
    if (auto E = ParseExpression()){
        // make an anonymous proto
        auto Proto = std::make_unique<PrototypeAST>("__anon_expr", std::vector<std::string>());
        return std::make_unique<FunctionAST>(move(Proto), move(E));
    }
    return nullptr;
}

//===----------------------------------------------------------------------===//
// Code Generation
//===----------------------------------------------------------------------===//


Value *LogErrorV(const char *Str){
    LogError(Str);
    return nullptr;
}

Value *NumberExprAST::codegen(){
    return ConstantFP::get(*TheContext, APFloat(Val));
}


Value *VariableExprAST::codegen(){
    // look this variable up in the function
    Value *V = NamedValues[Name];
    std::cout<<"VariableExprAST::codegen - Name:   "<<Name<<std::endl;
    if (!V)
        LogErrorV("Unknown variable name");

    return V;
}


Value *BinaryExprAST::codegen(){
    Value *L = LHS->codegen();
    Value *R = RHS->codegen();
    if (!L || !R)
        return nullptr;

    switch(Op){
    case '+':
        return Builder->CreateFAdd(L, R, "addtmp");
    case '-':
        return Builder->CreateFAdd(L, R, "subtmp");
    case '*':
        return Builder->CreateFMul(L, R, "multmp");
    case '<':
        L = Builder->CreateFCmpULT(L, R, "cmptmp");
        return Builder->CreateUIToFP(L, Type::getDoubleTy(*TheContext), "booltmp");

    default:
        return LogErrorV("invalid binary operator");
    }
}


Value *CallExprAST::codegen(){
    // look up the name in the global module table
    Function *CalleeF = TheModule->getFunction(Callee);
    if (!CalleeF)
        return LogErrorV("Unknown function referenced");

    // if argument mismatch error
    if (CalleeF->arg_size() != Args.size())
        return LogErrorV("Incorrect # arguments passed");

    std::vector<Value *> ArgsV;
    for (unsigned i=0, e=Args.size();i!=e;++i){
        ArgsV.push_back(Args[i]->codegen());
        if (!ArgsV.back()) // the last element
            return nullptr;
    }

    return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
}


Function *PrototypeAST::codegen(){
    // make the function type: double(double, double) etc.
    std::vector<Type *> Doubles(Args.size(),Type::getDoubleTy(*TheContext));

    FunctionType *FT = FunctionType::get(Type::getDoubleTy(*TheContext), Doubles, false);

    Function *F = Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get());


    //Set names for all arguments
    unsigned Idx = 0;
    for (auto &Arg: F->args())
        Arg.setName(Args[Idx++]);

    return F;
}


Function *FunctionAST::codegen(){
    //first, check for an existing function from a previous 'extern' declaration
    Function *TheFunction = TheModule->getFunction(Proto->getName());

    if (!TheFunction)
        TheFunction = Proto->codegen();

    if (!TheFunction)
        return nullptr;

    if (!TheFunction->empty())
        return (Function*)LogErrorV("Function cannot be redefined");


    // create a new bb to start insertion into
    BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction);
    Builder->SetInsertPoint(BB);

    // record the function arguments in the NamedValues map
    NamedValues.clear();
    for (auto &Arg: TheFunction->args())
        NamedValues[std::string(Arg.getName())] = &Arg;

    Value *RetVal = Body->codegen();
    if (RetVal){
        //finish off the function
        Builder->CreateRet(RetVal);

        //velidate the generated code, checking for consistency
        verifyFunction(*TheFunction);

        return TheFunction;
    }

    //error reading body ,remove funciton
    TheFunction->eraseFromParent();
    return nullptr;
}




// top-level parsing and jit driver
static void InitializeModule(){
    //Open a new context and module
    TheContext = std::make_unique<LLVMContext>();
    TheModule = std::make_unique<Module>("my cool jit", *TheContext);

    //create a new builder for the module
    Builder = std::make_unique<IRBuilder<>>(*TheContext);
}


static void HandleDefinition(){
    if(auto FnAST = ParseDefinition()){
        if (auto *FnIR = FnAST->codegen()){
            fprintf(stderr, "Read function definition: ");
            FnIR->print(errs());
            fprintf(stderr, "\n");
        }
    }
    else //skip token for error recovery
        getNextToken();
}

static void HandleExtern(){
    if (auto ProtoAST = ParseExtern()){
        if (auto *FnIR = ProtoAST->codegen()){
            fprintf(stderr, "Read extern: ");
            FnIR->print(errs());
            fprintf(stderr, "\n");
        }
    }
    else //skip token for error recovery
        getNextToken();
}

static void  HandleTopLevelExpression(){
    // evaluate a top-level expression into an anonymous function
    if (auto FnAST = ParseTopLevelExpr()){
        if (auto *FnIR = FnAST->codegen()){
            fprintf(stderr, "Read top-level expression: ");
            FnIR->print(errs());
            fprintf(stderr, "\n");

            // remove token for error recovery
            FnIR->eraseFromParent();
        }
    }
    else
        getNextToken();
}

// the driver
static void MainLoop(){
    while(true){
        fprintf(stderr, "read> ");
        switch(CurTok){
        case tok_eof:
            return;
        case ';': // ignore top-level semicolons
            getNextToken();
            break;
        case tok_def:
            HandleDefinition();
            break;
        case tok_extern:
            HandleExtern();
            break;
        default:
            HandleTopLevelExpression();
            break;
        }
    }
}

//Expression Code Generation

// int main(){
//     while(true){
//         int tok = gettok();
//         cout << "got token" << tok <<endl;
//     }
// }

int main(){
    fprintf(stderr, "ready> ");

    InitializeModule();
    getNextToken();
    MainLoop();

    // Print out all of the generated code.
    TheModule->print(errs(), nullptr);


    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: LLVM编译器实战教程版是一本介绍LLVM编译器的实践指南,书中详细介绍了如何使用LLVM编译器来构建高性能的编译器、工具和应用程序。该书主要分为三个部分,分别是LLVM基础知识、编译器中的LLVM和工具中的LLVM。 在LLVM基础知识部分,作者首先介绍了LLVM的概念和架构,并详细讲解了LLVM中的IR表示和优化。然后,作者介绍了如何使用LLVM来构建一个简单的编译器,包括如何实现词法分析、语法分析和代码生成。最后,作者讨论了如何使用LLVM来实现一些高级特性,例如JIT编译和LLVM IR的交互式优化。 在编译器中的LLVM部分,作者分别介绍了如何使用LLVM来开发C、C++和Objective-C编译器。该部分还讲解了如何使用LLVM来实现静态分析、污点分析和动态分析,并提供了一些实际的案例来帮助读者更好地理解。 在工具中的LLVM部分,作者介绍了如何使用LLVM来构建一些实用的工具,例如调试器、反汇编器和符号化工具。该部分还讨论了如何使用LLVM来实现自定义的代码生成器和目标描述文件,以及如何使用LLVM来增强其他编程语言和环境中的编译器。 总之,LLVM编译器实战教程版是一本非常实用且系统的指南,它能够帮助读者深入了解LLVM编译器的内部机制和应用方法,并且可以作为开发高性能编译器和工具的参考书籍。 ### 回答2: LLVM编译器实战教程版是一本面向开发者的指南,它旨在引导读者了解LLVM编译器的内部运作机制,以及如何为其开发可扩展的前端和后端工具。该教程深入浅出地讲解了LLVM编译器的内部结构,包括词法分析、语法分析和代码生成等内容。同时该书提供了许多有关如何使用LLVM开发编译器的实用示例和应用案例,包括如何构建前端和后端的IR、如何使用全局优化和代码生成等。总之,LLVM编译器实战教程版是一本很好的LLVM编译器实践指南,它对于那些想要了解LLVM编译器内部的人、想要使用LLVM开发编译器的人或想要利用LLVM的优化和代码生成技术的人,都是一本非常有价值的参考书籍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值