Toy 语言到 LLVM IR 实现源码注释

对从程序源代码到AST的转换部分做了注释

1,源码

toy.cpp

#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include <cctype>
#include <cstdio>
#include <map>
#include <string>
#include <vector>
//using namespace llvm;

enum Token_Type { EOF_TOKEN = 0, DEF_TOKEN, IDENTIFIER_TOKEN, NUMERIC_TOKEN };

FILE *file;// toy 语言源代码文件
static std::string Identifier_string;//用来存储 toy 语言程序中当前 get 到的标识符字符串
static int Numeric_Val;// 用来存储 toy 语言程序中的数值量

static int get_token()// 返回下一个当前的 token 类型,并改写全局变量 Identifier_str 或者 NumStr和Numeric_Val
{
  static int LastChar = ' ';

  while (isspace(LastChar))// 只要是空白键,就接着往下取,直到遇到非空白键字符才跳出循环
    LastChar = fgetc(file);

  if (isalpha(LastChar)) {// 如果是字母开头的字符串的话,有可能是普通标识符,也有可能是函数定义关键字 def
    Identifier_string = LastChar;
    while (isalnum((LastChar = fgetc(file))))//如果字符串接下来依然是字母或者数字,则继续读出并拼接在一起
      Identifier_string += LastChar;

    if (Identifier_string == "def")// 字符串可能是toy 语言关键字 def
      return DEF_TOKEN;

    return IDENTIFIER_TOKEN;//多数情况下是 普通标识符
  }

  if (isdigit(LastChar)) {//如果是数字开头的字符串的话,会是一个整数
    std::string NumStr;
    do {
      NumStr += LastChar;
      LastChar = fgetc(file);
    } while (isdigit(LastChar));

    Numeric_Val = strtod(NumStr.c_str(), 0);//将表示整数的字符串转换成整数
    return NUMERIC_TOKEN;
  }

  if (LastChar == '#') {// 整数开头的部分是注释
    do
      LastChar = fgetc(file);
    while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');// 三种情况下注释结束,文件末尾、行尾、回车

    if (LastChar != EOF)//如果一行注释结束了,但是下一行依然存在代码等,这是本函数还没有返回token type,则继续递归分析下一个字符串的token type 并返回这个token type
      return get_token();
  }

  if (LastChar == EOF)// 如果遇到了文件结尾
    return EOF_TOKEN;

  int ThisChar = LastChar;//既不是EOF,也不是字符串或数字,那可能是运算符;程序跑到本函数时,本程序还没有检查程序的正确性,故潜在认为这可能是个运算符;后便会检查
  LastChar = fgetc(file);
  return ThisChar;
}

namespace {

class BaseAST {// AST 基类,本类的子类都是一种句法结构的AST类;表达式AST,控制语句AST,
public:
  virtual ~BaseAST() {}
  virtual llvm::Value *Codegen() = 0;//每种具体的AST 都需要生成代码,故定义一个 interface
};

class NumericAST : public BaseAST {// 表示数值的 AST 类
  int numeric_val;

public:
  NumericAST(int val) : numeric_val(val) {}// 构造时赋值
  virtual llvm::Value *Codegen();
};

class VariableAST : public BaseAST {// 表示变量标识符的 AST
  std::string Var_Name;

public:
  VariableAST(const std::string &name) : Var_Name(name) {}
  virtual llvm::Value *Codegen();
};

class BinaryAST : public BaseAST {// 表示 二值运算符表达式的 AST
  std::string Bin_Operator;
  BaseAST *LHS, *RHS;// 分别指向两个操作数的 AST 实例,可能是NumericAST,有可能是 VariableAST,有可能是另一个BinaryAST,或者是 FunctionCallAST;这些AST都是最终返回数值,并且都继承了BaseAST

public:
  BinaryAST(std::string op, BaseAST *lhs, BaseAST *rhs)// 这意味着自底向上构造 AST,否则二值操作数的参数 AST 实例不存在
      : Bin_Operator(op), LHS(lhs), RHS(rhs) {}
  virtual llvm::Value *Codegen();
};

class FunctionCallAST : public BaseAST {// 调用函数的 AST,被调函数名和参数列表
  std::string Function_Callee;
  std::vector<BaseAST *> Function_Arguments;// 参数列表,每个参数都是一个 AST 实例

public:
  FunctionCallAST(const std::string &callee, std::vector<BaseAST *> &args)
      : Function_Callee(callee), Function_Arguments(args) {}
  virtual llvm::Value *Codegen();
};

class FunctionDeclAST {// 函数声明 AST 类,并没有继承 BaseAST; 因为仅仅做原型对比,故参数是字符串,toy中都是int型的,故只存名字串即可
  std::string Func_Name;
  std::vector<std::string> Arguments;

public:
  FunctionDeclAST(const std::string &name, const std::vector<std::string> &args)
      : Func_Name(name), Arguments(args){};
  llvm::Function *Codegen();
};

class FunctionDefnAST {// 函数定义的 AST 类,由 函数声明部分和函数体构成
  FunctionDeclAST *Func_Decl;
  BaseAST *Body; // 没有定义函数体的 AST 类,故这里的Body 可以只是指向一个表达式的 AST

public:
  FunctionDefnAST(FunctionDeclAST *proto, BaseAST *body)
      : Func_Decl(proto), Body(body) {}
  llvm::Function *Codegen();
};
} // namespace

static int Current_token;// 存储当前被分析的 token 的类型 enumerate 值;
static int next_token() { return Current_token = get_token(); }// 通过全局变量File指针等读取下一个 token

static std::map<char, int> Operator_Precedence;// 运算符优先级

static int getBinOpPrecedence() {// 获取二元运算符的优先级
  if (!isascii(Current_token))//参数检查, 二元运算符是个ascii 码, +-*/
    return -1;

  int TokPrec = Operator_Precedence[Current_token];
  if (TokPrec <= 0)
    return -1;
  return TokPrec;// 返回优先级
}

static BaseAST *expression_parser();// 声明表达式解析器函数,下面实现

static BaseAST *identifier_parser() {// 标识符解析器函数
  std::string IdName = Identifier_string;// 当前普通标识符串

  next_token();// 返回下一个当前的 token 类型,并改写全局变量 Identifier_str 或者 NumStr和Numeric_Val

  if (Current_token != '(')//如果 标识符的接下来的token 不是(, 那么这个标识符是一个变量,否则是一个函数
    return new VariableAST(IdName);// 创建变量的 AST

  next_token();// 走到这里,说明当前搞到了一个(,那么标识符 IdName 是一个函数名,接下来尝试获取参数

  std::vector<BaseAST *> Args;//函数参数可能有多个
  if (Current_token != ')') { // 如果get到的当前token 不是),说明参数不为空,而且当前token就是这个参数
    while (1) {
      BaseAST *Arg = expression_parser();// 函数调用时,每个参数都可能是一个表达式
      if (!Arg)// 什么情况为 NULL?
        return 0;
      Args.push_back(Arg);// 入列表

      if (Current_token == ')')//参数结束否
        break;

      if (Current_token != ',')// 为真时,出错;既不是)又不是,  是不符合语法要求的。
        return 0;
      next_token();
    }
  }
  next_token();

  return new FunctionCallAST(IdName, Args);
}

static BaseAST *numeric_parser() {
  BaseAST *Result = new NumericAST(Numeric_Val);
  next_token();
  return Result;
}

static BaseAST *paran_parser() {//如果遇到了表达式中含(, 则忽略这个(,并继续尝试解析接下来的表达式,可能会递归陷入,但最后每每都需要递归检查)的对应存在
  next_token();
  BaseAST *V = expression_parser();
  if (!V)
    return 0;

  if (Current_token != ')')
    return 0;
  return V;
}

static BaseAST *Base_Parser() {
  switch (Current_token) {
  default:
    return 0;
  case IDENTIFIER_TOKEN:
    return identifier_parser();//
  case NUMERIC_TOKEN:
    return numeric_parser();//创建数字 AST 实例
  case '(':
    return paran_parser();//陷入递归调用解析expression_parser()
  }
}
//这个函数比较复杂一些,需要处理递归的运算符;    x * 3 + y;   x + 3 * y;  x * (3+y); (x*3) + 6; (x + 3) * y
static BaseAST *binary_op_parser(int Old_Prec, BaseAST *LHS) {//binary_op_parser() -> paran_parser() -> expression_parser()
  while (1) {
    int Operator_Prec = getBinOpPrecedence();

    if (Operator_Prec < Old_Prec)
      return LHS;//最后返回的LHS是 LHS = new BinaryAST(sting, LHS, RHS);

    int BinOp = Current_token;
    next_token();

    BaseAST *RHS = Base_Parser();
    if (!RHS)
      return 0;

    int Next_Prec = getBinOpPrecedence();
    if (Operator_Prec < Next_Prec) {
      RHS = binary_op_parser(Operator_Prec + 1, RHS);
      if (RHS == 0)
        return 0;
    }

    LHS = new BinaryAST(std::to_string(BinOp), LHS, RHS);
  }
}

static BaseAST *expression_parser() {
  BaseAST *LHS = Base_Parser();//取运算符左边的变量(可能是数字,也可能是标识符变量),创建对应的 AST 实例,被LHS指向之。
  if (!LHS)
    return 0;
  return binary_op_parser(0, LHS);// 第一个参数作为binary op AST 的左操作数,同时解析出操作符和右边的操作数,右操作数有可能也是个表达式,故可能递归进expression_parser()的调用中
}

static FunctionDeclAST *func_decl_parser() {
  if (Current_token != IDENTIFIER_TOKEN)//函数名字,必须是个字符串
    return 0;

  std::string FnName = Identifier_string;//将当前token名字转存入 FnName,函数名字token
  next_token();//函数名后跟着个(

  if (Current_token != '(')//如果不是(,则出错
    return 0;

  std::vector<std::string> Function_Argument_Names;//开始解析函数参数列表
  while (next_token() == IDENTIFIER_TOKEN)// 要么参数列表非空,则取到参数名字;为空,则取到的token应该是).
    Function_Argument_Names.push_back(Identifier_string);//两个及以上参数之间用空格隔开,此处未处理“,”
  if (Current_token != ')')
    return 0;

  next_token();//拿到下一个token后,为函数返回做后续准备
  //创建函数声明的 AST,并返回之,可能作为函数 定义AST 实例的一个成员
  return new FunctionDeclAST(FnName, Function_Argument_Names);
}

static FunctionDefnAST *func_defn_parser() {
  next_token();//这是def之后的token,即函数名字,必须是个字符串
  FunctionDeclAST *Decl = func_decl_parser();
  if (Decl == 0)
    return 0;
  //在 expression_parser 运行之前,已经取到了下一个 token,发生在func_decl_parser()最后的部分
  if (BaseAST *Body = expression_parser())//def 后边必然先出现函数声明,再出现函数体,即表达式。
    return new FunctionDefnAST(Decl, Body);
  return 0;
}

static FunctionDefnAST *top_level_parser() {
  if (BaseAST *E = expression_parser()) {
    FunctionDeclAST *Func_Decl =
        new FunctionDeclAST("", std::vector<std::string>());
    return new FunctionDefnAST(Func_Decl, E);
  }
  return 0;
}

static void init_precedence() {
  Operator_Precedence['-'] = 1;
  Operator_Precedence['+'] = 2;
  Operator_Precedence['/'] = 3;
  Operator_Precedence['*'] = 4;
}

static llvm::Module *Module_Ob;
static llvm::LLVMContext MyGlobalContext;
static llvm::IRBuilder<> Builder(MyGlobalContext);
static std::map<std::string, llvm::Value *> Named_Values;

llvm::Value *NumericAST::Codegen() {//一个 Value 对象即一个 SSA 表达式,一个函数体,即一堆 Value 对象,即一堆 SSA 语句
  return llvm::ConstantInt::get(llvm::Type::getInt32Ty(MyGlobalContext), numeric_val);
  //IntegerType *Type::getInt32Ty(LLVMContext &C) { return &C.pImpl->Int32Ty; }
  //这个Codegen会返回NumericAST 中的确切数值;
}

// VariableAST 在 toy语言中,仅仅是函数参数;函数体只有一句,其中不定义变量,变量仅仅可以从函数参数而来
llvm::Value *VariableAST::Codegen() {
  llvm::Value *V = Named_Values[Var_Name];
  return V ? V : 0;
}

llvm::Value *BinaryAST::Codegen() {
  llvm::Value *L = LHS->Codegen();//zhege LHS有多种可能class的实例,但最后返回值都是Value
  llvm::Value *R = RHS->Codegen();
  if (L == 0 || R == 0)
    return 0;

  switch (atoi(Bin_Operator.c_str())) {
  case '+'://Builder.CreateAdd,会把L和R的 IR 放在上方,最后才是 Add 的 SSA IR 代码
    return Builder.CreateAdd(L, R, "addtmp");// SSA %addtmp#size() = Add int %L.Name %R.Name
  case '-':
    return Builder.CreateSub(L, R, "subtmp");
  case '*':
    return Builder.CreateMul(L, R, "multmp");
  case '/':
    return Builder.CreateUDiv(L, R, "divtmp");
  default:
    return 0;
  }
}

llvm::Value *FunctionCallAST::Codegen() {//一个 Value 对象即一个 SSA 表达式
  llvm::Function *CalleeF = Module_Ob->getFunction(Function_Callee);

  std::vector<llvm::Value *> ArgsV;
  for (unsigned i = 0, e = Function_Arguments.size(); i != e; ++i) {
    ArgsV.push_back(Function_Arguments[i]->Codegen());
    if (ArgsV.back() == 0)
      return 0;
  }

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

llvm::Function *FunctionDeclAST::Codegen() {// 对函数的创建的 LLVM IR 创建进全局变量 Module_Ob 中去
  std::vector<llvm::Type *> Integers(Arguments.size(),// 函数参数
                               llvm::Type::getInt32Ty(MyGlobalContext));//因为toy语言参数都是int类型,故定义参数个数个 Int32类型的指针;
  llvm::FunctionType *FT =// 函数类型=返回值+参数列表
      llvm::FunctionType::get(llvm::Type::getInt32Ty(MyGlobalContext), Integers, false);//functiontype::get()的第一个参数是返回值类型,第二个参数是参数vector
  llvm::Function *F =// 创建函数原型=返回值 + 函数名 + 参数列表,同时存进 Module_Ob 中去。
      llvm::Function::Create(FT, llvm::Function::ExternalLinkage, Func_Name, Module_Ob);//将函数名Func_Name 和 函数原型 FT,联合创建进入Module_Ob中去。看看怎么存储的。

  if (F->getName() != Func_Name) {//何时会true?
    F->eraseFromParent();
    F = Module_Ob->getFunction(Func_Name);

    if (!F->empty())
      return 0;

    if (F->arg_size() != Arguments.size())
      return 0;
  }

  unsigned Idx = 0;
  for (llvm::Function::arg_iterator Arg_It = F->arg_begin(); Idx != Arguments.size();
       ++Arg_It, ++Idx) {
    Arg_It->setName(Arguments[Idx]);//给 Value 实例设置名字,为变量名
    Named_Values[Arguments[Idx]] = Arg_It;//Arguments[Idx] 得到参数名字 String;Arg_It 是 Value 类型
  }

  return F;//返回函数原型的IR结构
}

llvm::Function *FunctionDefnAST::Codegen() {//由函数声明 Codegen 和 函数体的 binary op Codegen拼出来的
  Named_Values.clear();

  llvm::Function *TheFunction = Func_Decl->Codegen();//返回函数原型的IR内存表示
  if (TheFunction == 0)
    return 0;

  llvm::BasicBlock *BB = llvm::BasicBlock::Create(MyGlobalContext, "entry", TheFunction);//创建基本块
  Builder.SetInsertPoint(BB);

  if (llvm::Value *RetVal = Body->Codegen()) {//Body->Codegen 调用 binary op Codegen
    Builder.CreateRet(RetVal);
    verifyFunction(*TheFunction);
    return TheFunction;
  }

  TheFunction->eraseFromParent();
  return 0;
}

static void HandleDefn() {
  if (FunctionDefnAST *F = func_defn_parser()) {//先 构建toy 程序源码的 AST, 注意 AST 树的形状
    if (llvm::Function *LF = F->Codegen()) {//再 通过解析 AST 来生成 LLVM IR
    }
  } else {
    next_token();
  }
}

static void HandleTopExpression() {
  if (FunctionDefnAST *F = top_level_parser()) {
    if (llvm::Function *LF = F->Codegen()) {
    }
  } else {
    next_token();
  }
}

static void Driver() {// 完成对全部源文件的读入和 tokenize
  while (1) {
    switch (Current_token) {
    case EOF_TOKEN:// 源码文件结束,返回
      return;
    case ';':// 遇到;时,忽略,继续下一个 token
      next_token();
      break;
    case DEF_TOKEN:// 遇到 def token,接下来是构建函数定义
      HandleDefn();
      break;
    default:
      HandleTopExpression();//
      break;
    }
  }
}

extern "C" double putchard(double X) {
  putchar((char)X);
  return 0;
}

int main(int argc, char *argv[])
{
  llvm::LLVMContext &Context = MyGlobalContext;
  init_precedence();// 运算符优先级

  file = fopen(argv[1], "r");

  if (file == 0) {
    printf("Could not open file\n");
  }

  next_token();
  Module_Ob = new llvm::Module("my compiler", Context);// 第一个字符串参数是 module ID,; ModuleID = 'my compiler'
  Driver();
  Module_Ob->print(llvm::outs(), nullptr);
  // Module_Ob->dump();// 旧版 比如3.5 7.0 的输出方式,代替上一行的 print(...)

  return 0;
}

如果是比较久的llvm版本,比如llvm-3.5 则将倒数第2行代码替换成:

  Module_Ob->dump();

2, Makefile

LLVM_CONFIG ?= llvm-config
#CXX := clang++
ifndef VERBOSE
QUIET :=@
endif

SRC_DIR ?= $(PWD)
LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags) 
COMMON_FLAGS = -Wall -Wextra
CXXFLAGS += $(COMMON_FLAGS) $(shell $(LLVM_CONFIG) --cxxflags)
LCXX :=$(shell $(LLVM_CONFIG) --cxxflags)
CPPFLAGS += $(shell $(LLVM_CONFIG) --cppflags) -I$(SRC_DIR)

CLANGLIBS = \
  -Wl,--start-group \
  -lclang \
  -lclangFrontend \
  -lclangDriver \
  -lclangSerialization \
  -lclangParse \
  -lclangSema \
  -lclangAnalysis \
  -lclangEdit \
  -lclangAST \
  -lclangLex \
  -lclangBasic \
  -Wl,--end-group

LLVMLIBS = $(shell $(LLVM_CONFIG) --libs)

PROJECT = toy
PROJECT_OBJECTS = toy.o

default: $(PROJECT)

%.o : $(SRC_DIR)/%.cpp
	@echo Compiling $*.cpp
	$(QUIET)$(CXX) -g -c $(CPFLAGS) $(CXXFLAGS) $<

$(PROJECT) : $(PROJECT_OBJECTS) 
	@echo Linking $@
	$(QUIET)$(CXX) -g -o $@ $(LDFLAGS) $^ $(CLANGLIBS) $(LLVMLIBS) -lncurses

.PHONY: clean
clean:
	$(QUIET)rm -f $(PROJECT) $(PROJECT_OBJECTS)


.PHONY: echo
echo:
	@echo "CXX 	is	$(CXX)"
	@echo "LDFLAGS 	is	$(LDFLAGS)}"
	@echo "CXXFLAGS	is	$(CXXFLAGS)"
	@echo "CPPFLAGS	is	$(CPPFLAGS)"
	@echo "SRC_DIR	is	$(SRC_DIR)"



3, 运行效果

本运行测试是在llvm-18环境测试,更低的版本也没问题,指示如前所述,更改一句源码为dump()即可。

test case:

hello.t

def add (x y)
x + ( y +  77 );

实际的编译命令如下:

g++ -g -c  -Wall -Wextra -I/home/hipper/llvm_3_4_0_ex/browse_llvm_17/local_d/include -std=c++17   -fno-exceptions -funwind-tables -fno-rtti -D_GNU_SOURCE -D_DEBUG -D_GLIBCXX_ASSERTIONS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS /home/hipper/llvm_3_4_0_ex/browse_llvm_17/ex/toy_cooboo/toy.cpp
echo Linking toy
g++ -g -o toy -L/home/hipper/llvm_3_4_0_ex/browse_llvm_17/local_d/lib   toy.o -Wl,--start-group -lclang -lclangFrontend -lclangDriver -lclangSerialization -lclangParse -lclangSema -lclangAnalysis -lclangEdit -lclangAST -lclangLex -lclangBasic -Wl,--end-group -lLLVMWindowsManifest -lLLVMXRay -lLLVMLibDriver -lLLVMDlltoolDriver -lLLVMTextAPIBinaryReader -lLLVMCoverage -lLLVMLineEditor -lLLVMNVPTXCodeGen -lLLVMNVPTXDesc -lLLVMNVPTXInfo -lLLVMX86TargetMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMX86Desc -lLLVMX86Info -lLLVMOrcDebugging -lLLVMOrcJIT -lLLVMWindowsDriver -lLLVMMCJIT -lLLVMJITLink -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMDWP -lLLVMDebugInfoLogicalView -lLLVMDebugInfoGSYM -lLLVMOption -lLLVMObjectYAML -lLLVMObjCopy -lLLVMMCA -lLLVMMCDisassembler -lLLVMLTO -lLLVMPasses -lLLVMHipStdPar -lLLVMCFGuard -lLLVMCoroutines -lLLVMipo -lLLVMVectorize -lLLVMLinker -lLLVMInstrumentation -lLLVMFrontendOpenMP -lLLVMFrontendOffloading -lLLVMFrontendOpenACC -lLLVMFrontendHLSL -lLLVMFrontendDriver -lLLVMExtensions -lLLVMDWARFLinkerParallel -lLLVMDWARFLinkerClassic -lLLVMDWARFLinker -lLLVMGlobalISel -lLLVMMIRParser -lLLVMAsmPrinter -lLLVMSelectionDAG -lLLVMCodeGen -lLLVMTarget -lLLVMObjCARCOpts -lLLVMCodeGenTypes -lLLVMIRPrinter -lLLVMInterfaceStub -lLLVMFileCheck -lLLVMFuzzMutate -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoBTF -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMIRReader -lLLVMAsmParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMFuzzerCLI -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMTargetParser -lLLVMTableGen -lLLVMSupport -lLLVMDemangle -lncurses

4,另一个实验

观察一下 addtmp2的名字的代码,同名变量会被LLVM 自动补尾数

5, IRBuilder

未完待续 ... ...
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值