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

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

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

【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编译自定义语言【2】转llvm IR 【编译器】使用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编译自定义语言【3】编译 object
2025-03-28 · via Пусть этот камень будет более крепким, чем человек

# 前言

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

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

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

# 目标

再回忆一下顶级 Item 转换为 llvm IR 的过程,三种顶级 Item:函数 Function、外部函数 Extern、顶级表达式 TopLevelExpr。官方例子已经把这三种顶级 item 全部经过 Codegen 转换成 llvm IR 中的 Function。
Codegen层级

函数 Function 自然是转换为 llvm::Function ,顶级表达式 TopLevelExpr 转换为匿名原型 prototype 的 llvm::Function ,外部函数 Extern 转换为没有表达式 body 的 llvm::Function 。总之,前述介绍将所有顶级 Item 转换为了 llvm::Function
所以本篇内容的目的是,介绍如何将 llvm::Function 编译成 Object,进而执行的过程。

下面是一个将 llvm::Function 编译成 Object 的例子,实现 Hello World 的输出。前述流程已经到达 llvm::Function 这一步,为了避免复杂的 Lexer、AST、Codegen 的干扰而分散注意力,以下代码仅展示从 llvm::Function 开始编译成 Object 的流程。

#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Linker/Linker.h"
#include "llvm/Transforms/InstCombine/InstCombine.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Scalar/GVN.h"
#include <iostream>
#include <memory>
#include <optional>
#include "llvm/Support/FileSystem.h"
using namespace llvm;
int main() {
    
    LLVMContext Context;
    
    std::unique_ptr<Module> M = std::make_unique<Module>("MyModule", Context);
    
    IRBuilder<> Builder(Context);
    
    FunctionType *PutsFT = FunctionType::get(
        IntegerType::getInt32Ty(Context), {PointerType::getUnqual(IntegerType::getInt8Ty(Context))}, false);
    Function *PutsF = Function::Create(PutsFT, Function::ExternalLinkage, "puts", M.get());
    
    Constant *HelloStr = ConstantDataArray::getString(Context, "Hello, World", true);
    GlobalVariable *HelloGV = new GlobalVariable(
        *M, HelloStr->getType(), true, GlobalValue::InternalLinkage, HelloStr, "hello");
    
    Constant *Zero = ConstantInt::get(IntegerType::getInt32Ty(Context), 0);
    std::vector<Constant *> Indices = {Zero, Zero};
    Constant *HelloPtr = ConstantExpr::getGetElementPtr(HelloStr->getType(), HelloGV, Indices);
    
    FunctionType *MainFT = FunctionType::get(IntegerType::getInt32Ty(Context), false);    
    Function *MainF = Function::Create(MainFT, Function::ExternalLinkage, "main", M.get());
    
    BasicBlock *BB = BasicBlock::Create(Context, "entry", MainF);
    
    Builder.SetInsertPoint(BB);
    
    Builder.CreateCall(PutsF, {HelloPtr});
    
    Builder.CreateRet(ConstantInt::get(IntegerType::getInt32Ty(Context), 0));
    
    if (verifyModule(*M, &errs())) {
        errs() << "Module verification failed!\n";
        return 1;
    }
    
    InitializeAllTargetInfos();
    InitializeAllTargets();
    InitializeAllTargetMCs();
    InitializeAllAsmParsers();
    InitializeAllAsmPrinters();
    
    std::string TargetTriple = "x86_64-pc-linux-gnu"; 
    M->setTargetTriple(TargetTriple);
    std::string Error;
    const Target *TheTarget = TargetRegistry::lookupTarget(TargetTriple, Error);
    if (!TheTarget) {
        errs() << Error;
        return 1;
    }
    
    TargetOptions Opts;
    std::optional<Reloc::Model> RM;
    TargetMachine *TM = TheTarget->createTargetMachine(
        TargetTriple, "generic", "", Opts, RM);
    M->setDataLayout(TM->createDataLayout());
    
    std::error_code EC;
    raw_fd_ostream dest("output.o", EC, sys::fs::OpenFlags::OF_None);
    if (EC) {
        errs() << "Could not open file: " << EC.message();
        return 1;
    }
    
    legacy::PassManager PM;
    PM.add(createInstructionCombiningPass());
    PM.add(createReassociatePass());
    PM.add(createGVNPass());
    PM.add(createCFGSimplificationPass());
    
    if (TM->addPassesToEmitFile(PM, dest, nullptr, CodeGenFileType::ObjectFile)) {
        errs() << "TargetMachine can't emit a file of this type";
        return 1;
    }
    
    PM.run(*M);
    dest.flush();
    std::cout << "Object file generated: output.o" << std::endl;
    return 0;
}

运行方式:

/home/user/your/path/of/llvm/bin/clang++ -std=c++17 -fPIE HelloWorld.cpp `/home/user/your/path/of/llvm/bin/llvm-config --cxxflags --ldflags --system-libs --libs all` -o generate_ir
./generate_ir
clang++ output.o -o run.x
./run.x

通过以上命令编译代码,获取可执行文件 generate_ir 。执行可执行文件后输出编译结果 output.o ,最后编译和运行,能够输出 Hello, World!

# 代码解释

代码的详细解释可以看代码注释。这里只增添一些其他信息。

LLVMContext、Module 和 IRBuilder 是构建和操作 LLVM 中间表示(IR)的核心类。

LLVMContext 是 LLVM 库中最基础的类,它代表了一个独立的 LLVM 执行环境,负责管理 LLVM IR 中所有类型、常量和其他元数据的内存。

Module 表示一个完整的编译单元,类似于一个源文件编译后的结果。作为一个全局的容器,Module 包含了所有的全局变量、函数定义和声明。

IRBuilder 是一个用于生成 LLVM IR 指令的辅助类。提供了一系列的方法来创建各种 LLVM IR 指令,如算术运算、控制流指令等。
代码中还有一个 BasicBlock(基本块)的概念,它在很多场景下都需要被创建。BasicBlock 是一组顺序执行的指令序列,具有单一的入口和单一的出口。这意味着基本块内的指令会按照顺序依次执行,不会有分支跳转到基本块内部的中间位置,并且基本块的末尾通常是一条跳转指令(如 br 指令),用于控制程序流的转移。

什么时候需要创建基本块?

1. 创建函数时

当创建一个新的函数时,函数体需要由基本块组成。通常,函数至少需要一个入口基本块,用于开始执行函数的代码。比如代码中的 “main” 函数。

2. 实现控制流时

当程序中存在控制流语句(如 if-else、for 循环、while 循环等)时,需要创建多个基本块来表示不同的执行路径。

比如下面是一个简单的 while 循环示例,实现一个变量从 0 递增到 10 的过程。总之,创建 BasicBlock 主要是为了组织函数的执行逻辑,处理控制流语句和循环结构。

FunctionType *FT = FunctionType::get(Builder.getInt32Ty(), {Builder.getInt32Ty()}, false);
Function *F = Function::Create(FT, Function::ExternalLinkage, "test_if", M.get());
Argument *Arg = &*F->arg_begin();
BasicBlock *EntryBB = BasicBlock::Create(Context, "entry", F);
BasicBlock *TrueBB = BasicBlock::Create(Context, "true", F);
BasicBlock *FalseBB = BasicBlock::Create(Context, "false", F);
BasicBlock *MergeBB = BasicBlock::Create(Context, "merge", F);
Builder.SetInsertPoint(EntryBB);
Value *Cmp = Builder.CreateICmpSGT(Arg, Builder.getInt32(0));
Builder.CreateCondBr(Cmp, TrueBB, FalseBB);
Builder.SetInsertPoint(TrueBB);
Value *TrueResult = Builder.getInt32(1);
Builder.CreateBr(MergeBB);
Builder.SetInsertPoint(FalseBB);
Value *FalseResult = Builder.getInt32(0);
Builder.CreateBr(MergeBB);
Builder.SetInsertPoint(MergeBB);
PHINode *Phi = Builder.CreatePHI(Builder.getInt32Ty(), 2);
Phi->addIncoming(TrueResult, TrueBB);
Phi->addIncoming(FalseResult, FalseBB);
Builder.CreateRet(Phi);

# llvm IR 的形式

看了官方例子,也许你和我一样,也有这样的疑问:是不是所有的东西,转换为 llvm IR 之后,都是以函数 llvm::Function 的形式存在的?因为不仅是三种顶级 Item,还是 main 函数,都以 llvm::Function 的形式存在。

实际上不是。 llvm::Function 是主要的存在形式,但是还有全局变量(Global Variable)、常量(Constant)、基本块(Basic Block)、指令(Instruction)、元数据(Metadata)等。

想要真正了解 llvm IR 还得全面地学习。

# 后记

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