对于LLVM这样的编译框架来说,IR是非常重要的。LLVM IR(Intermediate Representation,中间表示)连接着编译器前端和编译器后端。IR的设计很大程度体现着LLVM插件化、模块化的设计哲学,LLVM的各种pass其实都是作用在LLVM IR上的。同时IR也是一个编译器组件接口。通常情况下,设计一门新的编程语言只需要完成能够生成LLVM IR的编译器前端即可,然后就可以轻松使用LLVM的各种编译优化、JIT支持、目标代码生成等功能。
LLVM IR的表示形式
LLVM IR有三种形式:
内存中的表示形式,如BasicBlock,Instruction这种cpp类;
bitcode形式,这是一种序列化的二进制表示形式;
LLVM汇编文件形式,这也是一种序列化的表示形式,与bitcode的区别是汇编文件是可读的、字符串的形式。
内存中的IR模型
内存中IR模型其实就是对应LLVM实现中的OO模型,更直白的就是一些cpp的class定义。
Module类,Module可以理解为一个完整的编译单元。一般来说,这个编译单元就是一个源码文件,如一个后缀为cpp的源文件。
Function类,这个类顾名思义就是对应于一个函数单元。Function可以描述两种情况,分别是函数定义和函数声明。
BasicBlock类,这个类表示一个基本代码块,“基本代码块”就是一段没有控制流逻辑的基本流程,相当于程序流程图中的基本过程(矩形表示)。
Instruction类,指令类就是LLVM中定义的基本操作,比如加减乘除这种算数指令、函数调用指令、跳转指令、返回指令等等。
出了上面这几个类之外,还有两个基本类型Value和User。
Value类,Value是一个非常基础的基类,一个继承于Value的子类表示它的结果可以被其他地方使用。
User类,一个继承于User的类表示它会使用一个或多个Value对象。
根据Value与User之间的关系,还可以引申出use-def链和def-use链这两个概念。use-def链是指被某个User使用的Value列表,def-use链是使用某个Value的User列表。实际上,LLVM中还定义了一个Use类,Use就是图中的一个边。
use-def,以函数与函数调用指令之间的关系为例。同一个函数实例可以在多个地方被调用。所以在LLVM中就可以通过以下方式查看一个函数被调用的指令列表。
Function *F = ...;
for (User *U : F->users()) {
if (Instruction *Inst = dyn_cast(U)) {
errs() << "F is used in instruction:\n";
errs() << *Inst << "\n";
}
}
def-use,以指令和指令操作数为例。一个指令可以有一个或多个操作数。以下代码可以遍历指令的操作数。
Instruction *pi = ...;
for (Use &U : pi->operands()) {
Value *v = U.get();
// ...
}
汇编形式的IR
如前文所说,汇编形式的IR是可读的。所以这里用一个简单的例子展示一下汇编形式的IR。首先编写一个简单的c语言函数如下:
// add.cpp
int add(int a, int b) {
return a + b;
}
使用如下命令可以产生汇编形式的IR:
clang add.cpp -emit-llvm -S -c -o add.ll
具体的汇编IR如下(有精简):
; ModuleID = 'add.cpp'
source_filename = "add.cpp"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"
; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @_Z3addii(i32, i32) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %3, align 4
%6 = load i32, i32* %4, align 4
%7 = add nsw i32 %5, %6
ret i32 %7
}
从第7~第16行就是add函数的汇编IR,还是具有一定可读性的。即使没有了解LLVM IR的具体指令定义,也大概能对应上源码与汇编IR之间的关系。
LLVM IR的生成
上面非常简单的介绍了LLVM IR的表示形式,这边想介绍一下IR具体的生成方式。LLVM提供了IR的构建接口,语言前端可以调用这些接口实现代码生成。IR的构建其实可以很复杂,但本文的定位仅仅是简介。所以这里会根据一个简单的例子,来介绍一下LLVM IR的构建过程。
// Created by InTheWorld on 2020/8/22.
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/Argument.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/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include
#include
#include
#include
using namespace llvm;
int main() {
LLVMContext Context;
// Create some module to put our function into it.
std::unique_ptr Owner = std::make_unique("test", Context);
Module *M = Owner.get();
// Create the add1 function entry and insert this entry into module M. The
// function will have a return type of "int" and take an argument of "int".
Function *Add1F =
Function::Create(FunctionType::get(Type::getInt32Ty(Context),
{Type::getInt32Ty(Context)}, false),
Function::ExternalLinkage, "add1", M);
// Add a basic block to the function. As before, it automatically inserts
// because of the last argument.
BasicBlock *BB = BasicBlock::Create(Context, "EntryBlock", Add1F);
// Create a basic block builder with default parameters. The builder will
// automatically append instructions to the basic block `BB'.
IRBuilder<> builder(BB);
// Get pointers to the constant `1'.
Value *One = builder.getInt32(1);
// Get pointers to the integer argument of the add1 function...
assert(Add1F->arg_begin() != Add1F->arg_end()); // Make sure there's an arg
Argument *ArgX = &*Add1F->arg_begin(); // Get the arg
ArgX->setName("AnArg"); // Give it a nice symbolic name for fun.
// Create the add instruction, inserting it into the end of BB.
Value *Add = builder.CreateAdd(One, ArgX);
// Create the return instruction and add it to the basic block
builder.CreateRet(Add);
// Now, function add1 is ready.
// Now we're going to create function `foo', which returns an int and takes no
// arguments.
Function *FooF =
Function::Create(FunctionType::get(Type::getInt32Ty(Context), {}, false),
Function::ExternalLinkage, "foo", M);
// Add a basic block to the FooF function.
BB = BasicBlock::Create(Context, "EntryBlock", FooF);
// Tell the basic block builder to attach itself to the new basic block
builder.SetInsertPoint(BB);
// Get pointer to the constant `10'.
Value *Ten = builder.getInt32(10);
// Pass Ten to the call to Add1F
CallInst *Add1CallRes = builder.CreateCall(Add1F, Ten);
Add1CallRes->setTailCall(true);
// Create the return instruction and add it to the basic block.
builder.CreateRet(Add1CallRes);
outs() << "We just constructed this LLVM module:\n\n" << *M;
outs().flush();
std::error_code error_code;
std::unique_ptr Out(new ToolOutputFile(
"./test.bc", error_code, sys::fs::F_None));
if (errorCodeToError(error_code)) {
errs() << errorCodeToError(error_code) << '\n'; return -1; } //write bitcode WriteBitcodeToFile(*M, Out->os());
Out->keep(); // Declare success
return 0;
}
这段代码是我基于LLVM项目中关于JIT功能的一段sample code改过来的。这段代码的目标很明确就是构建一个IR Module,这个Module由以下两个函数构成:
int add1(int x) {
return x+1;
}
int foo() {
return add1(10);
}
理解这段代码的方式就是把自己当做一个编译器前端,看着这两个函数。然后根据语义,把它们翻译成LLVM IR。41~56行是在构建add1的IR,60~73行是在构建foo的IR。
这段程序的最后会把IR的汇编形式打印出来,也会保存一份到test.bc这个文件里。其中控制台输出如下:
We just constructed this LLVM module:
; ModuleID = 'test'
source_filename = "test"
define i32 @add1(i32 %AnArg) {
EntryBlock:
%0 = add i32 1, %AnArg
ret i32 %0
}
define i32 @foo() {
EntryBlock:
%0 = tail call i32 @add1(i32 10)
ret i32 %0
}
是不是感觉很眼熟?与前面clang生成的IR是类似的。
test comment