惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Пусть этот камень будет более крепким, чем человек

【琐记】烟火与尘埃 【Triton】Triton实现矩阵乘 【LLM推理加速】FlashAttention 【LLM推理加速】PagedAttention 【LLM推理加速】Online Softmax LLM基础知识【1】 Transformer模型 【AI编译】LayerGroup Tiling Tile的疑惑和思考 【AI编译】深度优先的Tile调度,万事大吉? 【AI编译】多级流水线Tile调度策略 【CUDA C++】GPU内存使用【3】 【AI编译】Cache缓存地址映射 【CUDA C++】GPU存储【2】 【CUDA C++】GPU基本介绍【1】 【00】0序章-不受欢迎的来客 【转载】我来了——持续低熵 【Halide】调度优化【2】 【感想】写作进度报告5 【Halide】调度优化【1】 【转载】北大中文男足战报2 【BYOC】TVM切分子图 【转载】北大中文男足战报1 【AI编译】张量生命周期管理 SystemC 用寄存器同步建模方法 【脉动阵列】脉动阵列类型 【im2col】AScend conv accelerate 【感想】写作进度报告4 【BYOC】TVM添加自定义编译器 ccompiler 【感想】写作进度报告3 【Tengine】推理流程脑图【2】 【Tengine】推理流程脑图【1】 【NCNN】学习ncnn模型转换 【编译器】使用llvm编译自定义语言【3】编译 object 【编译器】使用llvm编译自定义语言【1】构建AST 【AI编译】如何进行内存分配 【感想】写作进度报告2 【AI编译】layer-group之后如何tiling 【AI编译】如何进行layer-group 【量化】连续卷积层首尾量化的可行性 【Gemm】内存对齐 【gemm】Gemm计算加速 【TVM】通过代码学习编译流程【5】FuseOps 【TVM】通过代码学习编译流程【6】CodeGen 【TVM】通过代码学习类【3.5】Pass 【TVM】通过代码学习编译流程【4】BuildRelay 【AI编译】Tiling操作能优化什么时间 【TVM】通过代码学习编译流程【3】模型编译 【TVM】通过代码学习编译流程【2】模型转换 【TVM】通过代码学习编译流程【1】必要知识 【感想】写作进度报告1 【Winograd】卷积加速算法原理及实现 SystemC 等待异步事件解决方案 【TVM】Python脚本实现模型编译和保存 【推理引擎】常见AI推理框架 【3D建模】T110E3卡迪夫蓝调皮肤模型 【TVM】C++部署运行TVM 【推理引擎】NCNN和Tengine量化推理逻辑对比 【3D建模】IS-7攻城锤流纹岩皮肤展示 【TVM】根据例子走通代码库 博客汇总目录 【Im2Col】卷积加速算法【2】NHWC 【Im2Col】卷积加速算法【1】 NCHW openBlas库的安装与简单使用 C语言工程调用Cpp库解决方案 foo Hello World
【编译器】使用llvm编译自定义语言【2】转llvm IR
Post author: XianMu@Пусть этот камень будет более крепким, чем ч · 2025-03-20 · via Пусть этот камень будет более крепким, чем человек

# 前言

本篇是使用 llvm 编译自定义语言的第二篇。前一篇【编译器】使用 llvm 编译自定义语言【1】文章自顶向下介绍了抽象语法树 AST 的构建过程,本篇接续上一篇,介绍将抽象语法树 AST 转化为 llvm IR 的过程。所使用代码例子来自 LLVM 官方教程 My First Language Frontend with LLVM Tutorial

代码请于 My First Language Frontend with LLVM Tutorial 下载。本篇介绍无法代替官方教程,感兴趣请参考 My First Language Frontend with LLVM Tutorial

作为初学者,错误在所难免,还望不吝赐教。

将抽象语法树 AST 转换为 LLVM IR 之前,初始化了以下 LLVM 提供的类。先简单了解一下这些类。

static std::unique_ptr<LLVMContext> TheContext;
static std::unique_ptr<Module> TheModule;
static std::unique_ptr<IRBuilder<>> Builder;
static std::map<std::string, Value *> NamedValues;
static std::unique_ptr<KaleidoscopeJIT> TheJIT;
static std::unique_ptr<FunctionPassManager> TheFPM;
static std::unique_ptr<LoopAnalysisManager> TheLAM;
static std::unique_ptr<FunctionAnalysisManager> TheFAM;
static std::unique_ptr<CGSCCAnalysisManager> TheCGAM;
static std::unique_ptr<ModuleAnalysisManager> TheMAM;
static std::unique_ptr<PassInstrumentationCallbacks> ThePIC;
static std::unique_ptr<StandardInstrumentations> TheSI;
static std::map<std::string, std::unique_ptr<PrototypeAST>> FunctionProtos;

1. LLVMContext 是 LLVM 中最基础的类之一,它代表了一个 LLVM 的上下文环境。在 LLVM 里,所有的核心数据结构(像 ModuleFunctionBasicBlock 等)都和一个特定的 LLVMContext 相关联。 LLVMContext 负责管理内存分配、常量池以及一些全局的元数据。
2. Module 依赖于 LLVMContext ,它是一个编译单元的表示,是包含多个函数、全局变量和类型定义的容器。在自定义语言编译过程中,生成的中间表示(IR)代码都会被添加到 Module 中。
3. IRBuilder 是一个用于构建 LLVM 中间表示(IR)代码的辅助类。借助 IRBuilder ,能够方便地创建各种 LLVM 指令,像算术运算、函数调用、控制流语句等。
4. KaleidoscopeJIT 是 LLVM 官方为简单的万花筒自定义语言设计的一个即时编译器(JIT),用于将生成的 LLVM IR 代码即时编译成机器码并执行。代码位于 llvm 工程: llvm-src/examples/Kaleidoscope/include/KaleidoscopeJIT.h
5. FunctionPassManager 是一个用于管理和执行函数级别的优化和转换的类。在 LLVM 里,优化和转换操作是通过一系列的 “Pass” 来实现的。 FunctionPassManager 可以将多个 Pass 组合起来,对函数进行一系列的优化,例如常量折叠、死代码消除等。
6. NamedValuesFunctionProtos 两个 Map 是自定义的,用于暂存变量、原型的容器。
7. 剩下的 LoopAnalysisManager 等都是用于分析优化的管理器。

# Codegen 顶级 Item

我们再看一下上一篇中的例子。例子中想要构建的三种顶级 Item:函数 Function、外部函数 Extern、顶级表达式 TopLevelExpr。

结构对照

现在的任务就是将这三种 AST 结构转换为 LLVM IR。

为了展示顶级 Item 的 Codegen 调用过程,我将调用过程划分为两个个层级,分别是高级 Codegen 和低级 Codegen,如图所示:

Codegen层级

根据图先来简单分析一下:函数 Function 这类顶级 Item 自然是调用 FunctionAST->Codegen 方法。而顶级表达式 TopLevelExpr 已经构建成一个匿名 Function,所以它和函数 Function 的结构是相同的,只是 Proto 参数为空,所以顶级表达式 TopLevelExpr 也调用 FunctionAST->Codegen 方法。

FunctionAST->Codegen 在构建过程中,会调用 PrototypeAST->CodegenExprAST->Codegen 方法,非常好理解,因为 Function 就包含原型和表达式两个结构。

外部函数 Extern 只有 Proto 结构,所以调用的是 PrototypeAST->Codegen 方法。

高级 Codegen 中, FunctionAST->Codegen 只是在调用另外两个, PrototypeAST->Codegen 非常简单, ExprAST->Codegen 实际上是在递归地调用低级 Codegen,所以相对复杂。

# Function

Function 的 Codegen 代码 (只保留了有助于理解的关键代码):

Function *FunctionAST::codegen() {
  
  
  auto &P = *Proto;
  FunctionProtos[Proto->getName()] = std::move(Proto);
  Function *TheFunction = getFunction(P.getName());   
  if (Value *RetVal = Body->codegen()) {  
    
    Builder->CreateRet(RetVal);  
    return TheFunction;
  }
  return nullptr;
}

FunctionAST->Codegen 调用 Prototype->Codegen ,获得一个配置了原型的 LLVM Function 类。然后调用 Body->codegen() 将表达式转换为 llvm IR ValueRetVal 。最后将 RetVal 设置为 Function 的返回值。

# Prototype

Prototype 的 Codegen 代码:

Function *PrototypeAST::codegen() {
  
  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()); 
  
  unsigned Idx = 0;
  for (auto &Arg : F->args())
    Arg.setName(Args[Idx++]);  
  return F;
}

PrototypeAST->Codegen 内容很简单,例如 foo(x) 这么一个原型,上述代码的过程就是 1. 声明一个参数 Double 2. 构建有一个 Double 返回值,并且名字为 foo 的函数 3. 设置参数的名字为 x

最后是 ExprAST->Codegen ,将表达式转换为 llvm IR,相对复杂,由下一节讲解。

# Codegen 表达式

Codegen层级

前述基本表达式有四种:数字表达式 NumberExpr 、二值表达式 BinaryExpr 、变量表达式 VariableExpr 、调用表达式 CallExpr 。所有的表达式都继承基类 ExprAST ,并重写了 Codegen() 函数。所以 ExprAST->Codegen 调用的是各个基本表达式的 Codegen() 函数。

# NumberExpr

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

NumberExprAST->codegen() 将本类的 Double 成员 Val 转化为 llvm IR 浮点值。

# BinaryExpr

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->CreateFSub(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");
  }
}

BinaryExprAST 有三个成员,分别是左表达式 LHS 和右表达式 RHS ,以及算子 OP 。左右表达式通过调用 ExprAST->codegen() 递归地实现构建过程。然后和 OP 一起组成二值表达式。

# CallExpr

Value *CallExprAST::codegen() {
  
  Function *CalleeF = getFunction(Callee);  
  if (!CalleeF)
    return LogErrorV("Unknown function referenced");
  
  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())
      return nullptr;
  }
  return Builder->CreateCall(CalleeF, ArgsV, "calltmp");  
}

CallAST 有两个成员,分别是调用名称 Callee 和参数 Args 。过程是 1. 根据调用名称寻找已经定义的函数。 2. 每个参数都是一个表达式,调用 ExprAST->codegen() 递归地实现构建过程,每个参数调用一次。 3. 组合起来,构建结束。

讲到这里,可能会有细心的读者发现:上一章讲的四个基本表达式:数字表达式 NumberExpr 、二值表达式 BinaryExpr 、调用表达式 CallExpr ,对应的 Codegen 分别有三个。那个变量表达式 VariableExpr 的 Codegen () 去哪儿了?

Value *VariableExprAST::codegen() {
  
  Value *V = NamedValues[Name];  
  if (!V)
    return LogErrorV("Unknown variable name");
  return V;
}

在这里,但是这里没有构建任何 llvm IR。

原因是在将 表达式转为 llvm IR 的过程中,没有任何新的变量需要创建,所需的变量在转换 Prototype 的时候就已经完成了。

例如 foo(x) x+2; 这样一个函数,变量 x 在转换 foo(x) 这个原型的时候就已经创建好了,所以在转换 x+2 表达式的时候,只需要在已经创建好的 NamedValues Map 中把它取出来。

# 后记

本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,并指明哪一篇博客,我看到一定及时回复!