LLVM系列第十五章:写一个简单的中间代码生成器IR Generator

2023-11-12

系列文章目录

LLVM系列第一章:编译LLVM源码
LLVM系列第二章:模块Module
LLVM系列第三章:函数Function
LLVM系列第四章:逻辑代码块Block
LLVM系列第五章:全局变量Global Variable
LLVM系列第六章:函数返回值Return
LLVM系列第七章:函数参数Function Arguments
LLVM系列第八章:算术运算语句Arithmetic Statement
LLVM系列第九章:控制流语句if-else
LLVM系列第十章:控制流语句if-else-phi
LLVM系列第十一章:写一个Hello World
LLVM系列第十二章:写一个简单的词法分析器Lexer
LLVM系列第十三章:写一个简单的语法分析器Parser
LLVM系列第十四章:写一个简单的语义分析器Semantic Analyzer
LLVM系列第十五章:写一个简单的中间代码生成器IR Generator
LLVM系列第十六章:写一个简单的编译器
LLVM系列第十七章:for循环
LLVM系列第十八章:写一个简单的IR处理流程Pass
LLVM系列第十九章:写一个简单的Module Pass
LLVM系列第二十章:写一个简单的Function Pass
LLVM系列第二十一章:写一个简单的Loop Pass
LLVM系列第二十二章:写一个简单的编译时函数调用统计器(Pass)
LLVM系列第二十三章:写一个简单的运行时函数调用统计器(Pass)
LLVM系列第二十四章:用Xcode编译调试LLVM源码
LLVM系列第二十五章:简单统计一下LLVM源码行数
LLVM系列第二十六章:理解LLVMContext
LLVM系列第二十七章:理解IRBuilder
LLVM系列第二十八章:写一个JIT Hello World
LLVM系列第二十九章:写一个简单的常量加法“消除”工具(Pass)

flex&bison系列



前言

在此记录下,基于LLVM写一个简单的中间代码生成器(Simple IR Generator)的过程,以备查阅。

开发环境的配置请参考 《LLVM系列第一章:编译LLVM源码》。

我们再来简单复习一下,编译器前端的流程:

词法分析Lexical
语法分析Syntax
语义分析Semantic
中间代码IR

更多关于编译器前端的介绍,请参看《LLVM系列第三章:写一个简单的词法分析器Lexer》。

本章内容仅与中间代码的生成(Intermediate Code Generation)有关,是一个简单的示例而已。其它与词法分析(Lexical Analysis)、语法分析(Syntax Analysis)以及语义分析(Semantic Analysis)相关的文章,请参看前面的章节。

一、SimpleLang语言

为了方便起见,我们自己定义一种很简单的语言(名为SimpleLang)如下(示例):

calc : ("with" ident ("," ident)* ":")? expr ;
expr: term(("+"|"-")term)* ;
term : factor (( "*" | "/") factor)* ;
factor : ident | number | "(" expr ")" ;
ident : ([a-zAZ])+ ;
number : ([0-9])+ ;

这也是我们在前面章节中用到的语言。

二、项目结构

我们把这个简单的项目命名为SimpleSemanticAnalyzer。项目组织结构与前几章的项目类似,具体如下(示例):

% tree -I "build|build-xcode"

.
├── CMakeLists.txt
├── README.md
└── src
    ├── AST.h
    ├── CMakeLists.txt
    ├── IRGenerator.cpp
    ├── IRGenerator.h
    ├── IRGeneratorPlayer.cpp
    ├── Lexer.cpp
    ├── Lexer.h
    ├── Parser.cpp
    ├── Parser.h
    ├── SemanticAnalyzer.cpp
    └── SemanticAnalyzer.h

各文件的内容大体如下:

  1. AST.h,SimpleLang语言的抽象语法树(AST)的定义及实现代码。
  2. Lexer.h和Lexer.cpp,SimpleLang语言的词法分析器(Lexer)的定义及实现代码。
  3. Parser.h和Parser.cpp,SimpleLang语言的语法分析器(Parser)的定义及实现代码。
  4. SemanticAnalyzer.h和SemanticAnalyzer.cpp,SimpleLang语言的语义分析器(Semantic Analyzer)的定义及实现代码。
  5. IRGenerator.h和IRGenerator.cpp,中间代码(Intermediate Representation,即IR)生成器的定义及实现代码。这是本章的重点。
  6. IRGeneratorPlayer.cpp,main函数,即IRGenerator的测试代码。

三、项目细节

1. 程序模块

这个简单的项目只包含了一个模块:

  1. SimpleSemanticAnalyzer,一个可执行程序文件

以下是跟项目组织结构相关的部分CMake脚本,与前一章的CMake脚本类似。

(1) 项目根目录(示例):

# CMakeLists.txt

...
project ("SimpleIRGenerator")
...
add_subdirectory ("src")

这里创建了一个项目(project),并把src目录下的子项目加入进来。

(2) src目录(示例):

# src/CMakeLists.txt

...
add_executable(SimpleIRGenerator ...)
...

这是src目录下的子项目,用来构建SimpleIRGenerator程序。

2. 引入LLVM

我们需要做一些与LLVM相关的配置,才能顺利地使用LLVM,具体做法请参看前面章节。

3. Simple IR Generator

词法、语法、语义分析的相关代码已在前面章节介绍,本章的重点在中间代码的生成上:

  1. src/SimpleIRGeneratorPlayer.cpp,包含了main函数,即测试代码
  2. src/SimpleIRGenerator.h,src/SimpleIRGenerator.cpp,包含了中间代码生成器的定义及实现代码

main函数(示例):

#include "IRGenerator.h"
...
static llvm::cl::opt<std::string> input(llvm::cl::Positional, llvm::cl::desc("<input expression>"), llvm::cl::init(""));

int main(int argc, const char** argv)
{
    llvm::InitLLVM llvmInitializer(argc, argv);
    llvm::cl::ParseCommandLineOptions(argc, argv, "SimpleParser - a simple code parser\n");
    llvm::outs() << "Input: \"" << input << "\"\n\n";

    Lexer lexer(input);
    Parser parser(lexer);
    AST* tree = parser.Parse();
    ...
    SemanticAnalyzer semanticAnalyzer;
    semanticAnalyzer.Analysis(tree)
    ...
    IRGenerator irGenerator;
    irGenerator.Generate(tree);
    ...
}

我们看到以上代码调用了IRGenerator来生成IR代码。IRGenerator的定义如下(示例):

class IRGenerator
{
public:

    void Generate(AST* Tree);
};

定义是很简单的,实现如下(示例):

namespace
{
    class IRGeneratorImplementation : public ASTVisitor
    {
        Module* module;
        IRBuilder<> builder;
        Type* voidType;
        Type* int32Type;
        Type* int8PtrType;
        Type* int8PtrPtrType;
        Constant* int32Zero;

        Value* value;
        StringMap<Value*> nameMap;

    public:

        IRGeneratorImplementation(Module* inModule) :
            module(inModule),
            builder(inModule->getContext())
        {
            voidType = Type::getVoidTy(module->getContext());
            int32Type = Type::getInt32Ty(module->getContext());
            int8PtrType = Type::getInt8PtrTy(module->getContext());
            int8PtrPtrType = int8PtrType->getPointerTo();
            int32Zero = ConstantInt::get(int32Type, 0, true);
        }

        void Generate(AST* tree)
        {
            FunctionType* mainFunctionType = FunctionType::get(int32Type, {int32Type, int8PtrPtrType}, false);
            Function* mainFunction = Function::Create(mainFunctionType, GlobalValue::ExternalLinkage, "main", module);
            BasicBlock* basicBlock = BasicBlock::Create(module->getContext(), "entry", mainFunction);
            builder.SetInsertPoint(basicBlock);

            tree->Accept(*this);

            FunctionType* calculatorWriteFunctionType = FunctionType::get(voidType, {int32Type}, false);
            Function* calculatorWriteFunction =
                Function::Create(calculatorWriteFunctionType, GlobalValue::ExternalLinkage, "calc_write", module);
            builder.CreateCall(calculatorWriteFunctionType, calculatorWriteFunction, {value});

            builder.CreateRet(int32Zero);
        }

        void Visit(Factor& node) override
        {
            if (node.GetType() == Factor::kIdent)
            {
                value = nameMap[node.GetValue()];
            }
            else
            {
                int intValue;
                node.GetValue().getAsInteger(10, intValue);
                value = ConstantInt::get(int32Type, intValue, true);
            }
        };

        void Visit(BinaryOp& node) override
        {
            node.GetLeft()->Accept(*this);
            Value* left = value;

            node.GetRight()->Accept(*this);
            Value* right = value;

            switch (node.GetOperator())
            {
            case BinaryOp::kPlus:
                value = builder.CreateNSWAdd(left, right);
                break;

            case BinaryOp::kMinus:
                value = builder.CreateNSWSub(left, right);
                break;

            case BinaryOp::kMultiple:
                value = builder.CreateNSWMul(left, right);
                break;

            case BinaryOp::kDivide:
                value = builder.CreateSDiv(left, right);
                break;
            }
        };

        void Visit(WithDeclaration& node) override
        {
            FunctionType* calculatorReadFunctionType = FunctionType::get(int32Type, {int8PtrType}, false);
            Function* calculatorReadFunction =
                Function::Create(calculatorReadFunctionType, GlobalValue::ExternalLinkage, "calc_read", module);
            for (const auto& variable : node)
            {
                // Create call to calc_read function
                Constant* strText = ConstantDataArray::getString(module->getContext(), variable);
                GlobalVariable* str = new GlobalVariable(*module,
                                                         strText->getType(),
                                                         /*isConstant=*/true,
                                                         GlobalValue::PrivateLinkage,
                                                         strText,
                                                         Twine(variable).concat(".str"));
                Value* ptr = builder.CreateInBoundsGEP(str, {int32Zero, int32Zero}, "ptr");
                CallInst* call = builder.CreateCall(calculatorReadFunctionType, calculatorReadFunction, {ptr});

                nameMap[variable] = call;
            }

            node.GetExpr()->Accept(*this);
        };
    };
} // namespace

void IRGenerator::Generate(AST* tree)
{
    LLVMContext context;
    Module* module = new Module("calc.expr", context);
    IRGeneratorImplementation generator(module);
    generator.Generate(tree);
    module->print(outs(), nullptr);
}

注意到,与前几章相比,我在这里调用了比较多的LLVM API。虽然这只是个示例程序而已,但是牵涉的LLVM相关的API已经不少了。

首先IRGenerator是AST的子类,关于抽象语法树(AST)的定义及实现请参考前面章节。这个示例中,IRGenerator需要遍历AST的节点,并为每个节点生成相应的IR代码。而具体的遍历及IR的生成工作,我们交给了IRGeneratorImplementation来做:

ASTVisitor
+Visit(AST&)
+Visit(Expr&)
+Visit(Factor&)
+Visit(BinaryOp&)
+Visit(WithDeclaration&)
IRGeneratorImplementation
+Visit(Factor&)
+Visit(BinaryOp&)
+Visit(WithDeclaration&)

IRGenerator首先创建了llvm::Module,这是IR代码组织结构中很重要基本的组成单元。Module中可以包含函数定义及实现,而函数中则包含了基本代码块BasicBlock,而BasicBlock则包含了一行行的代码,比如变量赋值、数学运算表达式、函数调用、函数返回等等。当然,所有这些都是可以用IR代码表达出来。

四、编译

1. 生成项目文件

用CMake生成项目文件(示例):

mkdir build
cd build

cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..

输出log如下(示例):

-- The C compiler identification is AppleClang 13.0.0.13000029
-- The CXX compiler identification is AppleClang 13.0.0.13000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found ZLIB: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/lib/libz.tbd (found version "1.2.11") 
-- Found LibXml2: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/lib/libxml2.tbd (found version "2.9.4") 
Found LLVM 12.0.1, build type Release
-- Configuring done
-- Generating done
-- Build files have been written to: .../SimpleIRGenerator/build

如果要生成Xcode项目文件,我们稍微改一下cmake命令的参数即可(示例):

mkdir build-xcode
cd build-xcode

cmake -G Xcode -DCMAKE_BUILD_TYPE=Debug ..

2. 编译

在编译之前,我们可以用clang-format工具把代码美化一下(示例):

cd /path/to/SimpleIRGenerator

clang-format -i src/*.cpp src/*.h

用ninja进行编译(示例):

cd /path/to/SimpleIRGenerator/build

ninja

输出log如下(示例):

[6/6] Linking CXX executable src/SimpleIRGenerator

3. 运行

运行SimpleIRGenerator(示例):

src/SimpleIRGenerator "with abc,xyz: (abc+xyz)*3 - 10/abc"

我们用于测试的SimpleLang程序代码,就这么简单的一句而已with abc,xyz: (abc+xyz)*3 - 10/abc。输出结果如下(示例):

Input: "with abc,xyz: (abc+xyz)*3 - 10/abc"

; ModuleID = 'calc.expr'
source_filename = "calc.expr"

@abc.str = private constant [4 x i8] c"abc\00"
@xyz.str = private constant [4 x i8] c"xyz\00"

define i32 @main(i32 %0, i8** %1) {
entry:
  %2 = call i32 @calc_read(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @abc.str, i32 0, i32 0))
  %3 = call i32 @calc_read(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @xyz.str, i32 0, i32 0))
  %4 = add nsw i32 %2, %3
  %5 = mul nsw i32 %4, 3
  %6 = sdiv i32 10, %2
  %7 = sub nsw i32 %5, %6
  call void @calc_write(i32 %7)
  ret i32 0
}

declare i32 @calc_read(i8*)

declare void @calc_write(i32)

可以看到,中间代码生成器可以为我们的SimpleLang语言生成LLVM IR代码了。

五、总结

我们主要基于LLVM提供的API,用C++写了一个很简单的中间代码(IR)生成器。完整源码示例请参看:
https://github.com/wuzhanglin/llvm-simple-IR-generator

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LLVM系列第十五章:写一个简单的中间代码生成器IR Generator 的相关文章

随机推荐

  • 缓存系列文章--8.热点key问题(mutex key)

    转载请注明出处哈 http carlosfu iteye com blog 2269678 一 引出热点key问题 我们通常使用 缓存 过期时间的策略来帮助我们加速接口的访问速度 减少了后端负载 同时保证功能的更新 一般情况下这种模式已经基
  • 2020-04-21

    实验2 3 1 求1到100的和 10分 本题要求编写程序 计算表达式 1 2 3 100 的值 输入格式 本题无输入 输出格式 按照以下格式输出 sum 累加和 代码 int main int i 0 int sum 0 for i 0
  • Numpy--布尔索引

    布尔索引 布尔索引是通过相同数组上的True还是False来进行提取的 提取的条件可以有多个 那么如果有多个 可以使用 来代表且 用来代表或 如果有多个条件 那么每个条件要使用圆括号括起来 布尔运算也是矢量的 比如以下代码 a1 np ar
  • IOS拉伸圆角图片

    UIImage buttonImageNormal UIImage imageNamed whiteButton png UIImage stretchableButtonImageNormal buttonImageNormal stre
  • String,StringBuffer,StringBuilder三者的异同?

    String StringBuffer StringBuilder三者的异同 String 不可变的字符序列 底层使用char 存储 StringBUffer 可变的字符序列 线程安全的 效率低 底层使用char 存储 StringBuil
  • 刀片服务器做虚拟机,刀片服务器Vmware虚拟化部署经验分享(初稿).docx

    HP C7000刀片服务器虚拟机部署经验分享 本文主要介绍一台新的 HP C7000 刀片服务器从上架加电到进行虚拟机部署的主要步骤 第一部分 为刀片系统配置远程管理 首先附 C7000刀片服务器各组件示意图一张 注意图中的 Insight
  • 华为云服务器免费用,CDN免费用,数据库免费用,免费免费,全场免费

    华为云免费专区 全场免费 自从华为云17年进军公有云市场以来 市场份额增长神速 20年华为云以高达259 6 的同比增长速度 已然稳定公有云市场前三的位置 但很多人依然没有试用过华为云的产品 即便性价比很高 也不敢贸然更换厂商 俗话说是骡子
  • 【ubuntu】Anaconda 安装+环境配置:pytorch1.5.0+torchvision0.6.0+cudatoolkit10.1

    1 正确安装anaconda 1 下载对应Anaconda版本 以Anaconda3 5 3 1 Linux x86 64 sh为例 Anaconda下载连接 2 命令安装 bash Anaconda3 5 3 1 Linux x86 64
  • C语言文件详解(超级详细,记得收藏~~~)

    什么是文件 磁盘上的文件是文件 在程序设计中 我们一般读的文件有两种 程序文件 和 数据文件 程序文件包括源程序文件 后缀为 c 目标文件 win下后缀为 obj 可执行文件 win下环境后缀为 exe 数据文件 文件的内容不一定是程序 而
  • java oauth2登录以及权限_OAuth2实现单点登录SSO

    1 前言 技术这东西吧 看别人写的好像很简单似的 到自己去写的时候就各种问题 一看就会 一做就错 网上关于实现SSO的文章一大堆 但是当你真的照着写的时候就会发现根本不是那么回事儿 简直让人抓狂 尤其是对于我这样的菜鸟 几经曲折 终于搞定了
  • WebSocket和HTTP的区别及原理

    HTTP协议 HTTP是单向的 客户端发送请求 服务器发送响应 举例来说 当客户端向服务器发送请求时 该请求以HTTP或HTTPS的形式发送 在接收到请求后 服务器会将响应发送给客户端 每个请求都与一个对应的响应相关联 在发送响应后客户端与
  • Jni基础

    1 JNI 的一般开发流程 1 1 定义好本地的 native 方法 package com darren ndk day13 import java util UUID public class Simple1 public static
  • 【转码方式】-Base64

    Base64 作用 在数据传输过程中 如果报文中存在英文字母以外的字符 就会出现乱码 如中文 图片 或者二进制报文 此时就可以通过Base64将不规则的数据流转化成Base64规定的64个可打印的字符 提高数据的可读性和可打印性 转码原理
  • c++实现合并两个无序数组并以从大到小的顺序排序

    今天做了一下途游游戏的线上笔试题 题目中有一个是合并两个无序数组并排序 从大到小 在这里写一下我的思路 如果有更简单思路的大神请给我留言 我首先想到的一个思路就是把A和B先放在同一个数组里 在随便用一个排序算法对新数组进行排序 这个方法时间
  • Shell脚本中2>&1、>、>>等符号到底是什么含义

    场景 在Linux Shell命令中 我们经常会遇到命令中类似这样的 gt 2 gt 1 符号 那么这些符号是什么含义 有什么用处呢 下面一起来看下 概念 在Linux shell中 0 1 2代表文件描述符 名称 代码 操作符 Java中
  • C++多种解法求最大回文子串

    题目 给定一字符串 求最长的回文子串 解法一 暴力法 循环查找字符串中的所有回文子串 时间复杂度O N3 第一遍循环 选取开始点 i 第二遍循环 选取结束位置 j 第三遍循环 判断 i j 是否为回文字符串 int palindromeA
  • Mysql服务器的外部连接

    目录 前言 Windows上的客户端连接工具有 Linux上连接其他虚拟机上的MySQL服务器 在Windows上通过Navicat连接虚拟机上的Mysql服务器 1 我们先下载好Navicat 2 当我们下载安装好Navicat后 打开它
  • 用org.apache.tools.zip压缩/解压缩zip文件

    package org coolyongzi import java io File import java io FileInputStream import java io FileOutputStream import java io
  • android删除文件夹代码,Android 删除指定文件代码

    package com tware pdfdrop import java io File import android app Activity import android graphics Color import android o
  • LLVM系列第十五章:写一个简单的中间代码生成器IR Generator

    系列文章目录 LLVM系列第一章 编译LLVM源码 LLVM系列第二章 模块Module LLVM系列第三章 函数Function LLVM系列第四章 逻辑代码块Block LLVM系列第五章 全局变量Global Variable LLV