0%

Introduce to LLVM C API

這篇是How to get started with the LLVM C API的讀後感,不過用我自己的方式表達。

先講結論

我重寫了程式碼,放在整篇文章的最後面,先看輸出結果。再回頭看程式碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cc `llvm-config --cflags` -c sum.c
$ c++ `llvm-config --cxxflags --ldflags --libs core executionengine jit interpreter analysis native bitwriter --system-libs` sum.o -o sum
$ ./sum 42 99
141
$ llvm-dis sum.bc
$ cat sum.ll
; ModuleID = 'sum.bc'

define i32 @sum(i32, i32) {
entry:
%tmp = add i32 %0, %1
ret i32 %tmp
}

這邊可以看兩個部份, Bitcode內容,以及JIT技術。之前介紹過LLVM的Bitcode,利用LLVM API可以生成Bitcode

建立Module

這邊就不特別提了,原來的連結寫得比較清楚。

1
LLVMModuleRef mod = LLVMModuleCreateWithName("my_module");

Bitcode生成

建立Function signature

1
2
3
4
5
6
LLVMValueRef createSumFunc(LLVMModuleRef mod)
{
LLVMTypeRef param_types[] = { LLVMInt32Type(), LLVMInt32Type() };
LLVMTypeRef ret_type = LLVMFunctionType(LLVMInt32Type(), param_types, ArraySize(param_types), 0);
return LLVMAddFunction(mod, "sum", ret_type);
}

顧名思義,sum就是有兩個int參數,然後輸出一個int參數的Function,我們先建立奇對應的Signature,然後把Bitcode填入其中。
param_type就是輸入參數,而ret_type就是想要的輸出參數,利用LLVMFunctionType將奇關聯起來,最後呼叫LLVMAddFunction建立一個LLVMValueRef物件。

如果對照Bitcode內容的話,就是以下這段

define i32 @sum(i32, i32)

填入IR Code

1
2
3
4
5
6
7
8
9
void implementIR(LLVMValueRef sum)
{
LLVMBasicBlockRef entry = LLVMAppendBasicBlock(sum, "entry");
LLVMBuilderRef builder = LLVMCreateBuilder();
LLVMPositionBuilderAtEnd(builder, entry);
LLVMValueRef tmp = LLVMBuildAdd(builder, LLVMGetParam(sum, 0), LLVMGetParam(sum, 1), "tmp");
LLVMBuildRet(builder, tmp);
LLVMDisposeBuilder(builder);
}

上面的第一行sum函數中間,新增一個Label,叫做entry。回頭看Bitcode

define i32 @sum(i32, i32) {
entry:
}

接著校新增真正的IR Code了,需要一個IRBuilder,然後連結到我們想要填Code的位置,也就是上面的第二行第三行。
接著需要IR Code之中的加法運算,以及Value Return。也就是上面的第四第五行。之後釋放掉IRBuilder的資源。

define i32 @sum(i32, i32) {
entry:
%tmp = add i32 %0, %1
ret i32 %tmp
|

之後呼叫

1
2
3
4
5
6
7
void dumpBitCode(LLVMModuleRef mod)
{
// Write out bitcode to file
if (LLVMWriteBitcodeToFile(mod, "sum.bc") != 0) {
fprintf(stderr, "error writing bitcode to file, skipping\n");
}
}

就能輸出完整的Bitcode了,這些跟JIT無關,可以單獨使用。

JIT CCode Generation

JIT Environment setup

在使用JIT之前,要先把環境準備好

1
2
3
4
5
6
7
8
LLVMLinkInJIT();
LLVMInitializeNativeTarget();
LLVMExecutionEngineRef engine;
char *error = NULL;
if (LLVMCreateExecutionEngineForModule(&engine, mod, &error) != 0) {
fprintf(stderr, "failed to create execution engine\n");
abort();
}

記住使用完後要將engine的資源釋放。

將外部的資料結構轉成LLVM所看得懂得資訊

1
2
3
4
LLVMGenericValueRef args[] = {
LLVMCreateGenericValueOfInt(LLVMInt32Type(), x, 0),
LLVMCreateGenericValueOfInt(LLVMInt32Type(), y, 0)
};

x, y是外部的參數,而args是餵進去給JIT Function的LLVM參數。

執行JIT Function

1
LLVMGenericValueRef res = LLVMRunFunction(engine, sum, ArraySize(args), args);

sum是我們的IR Function,args是輸入參數。

將LLVM得到的結果傳回外部

1
printf("%d\n", (int)LLVMGenericValueToInt(res, 0));

Full Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* LLVM equivalent of:
*
* int sum(int a, int b) {
* return a + b;
* }
*/

#include <llvm-c/Core.h>
#include <llvm-c/ExecutionEngine.h>
#include <llvm-c/Target.h>
#include <llvm-c/Analysis.h>
#include <llvm-c/BitWriter.h>

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

#define ArraySize(a) (sizeof(a) / sizeof(a[0]))

LLVMValueRef createSumFunc(LLVMModuleRef mod)
{
LLVMTypeRef param_types[] = { LLVMInt32Type(), LLVMInt32Type() };
LLVMTypeRef ret_type = LLVMFunctionType(LLVMInt32Type(), param_types, ArraySize(param_types), 0);
return LLVMAddFunction(mod, "sum", ret_type);
}

void implementIR(LLVMValueRef sum)
{
LLVMBasicBlockRef entry = LLVMAppendBasicBlock(sum, "entry");
LLVMBuilderRef builder = LLVMCreateBuilder();
LLVMPositionBuilderAtEnd(builder, entry);
LLVMValueRef tmp = LLVMBuildAdd(builder, LLVMGetParam(sum, 0), LLVMGetParam(sum, 1), "tmp");
LLVMBuildRet(builder, tmp);
LLVMDisposeBuilder(builder);
}

void dumpBitCode(LLVMModuleRef mod)
{
// Write out bitcode to file
if (LLVMWriteBitcodeToFile(mod, "sum.bc") != 0) {
fprintf(stderr, "error writing bitcode to file, skipping\n");
}
}

int main(int argc, char const *argv[]) {
LLVMModuleRef mod = LLVMModuleCreateWithName("my_module");
LLVMValueRef sum = createSumFunc(mod);
implementIR(sum);
dumpBitCode(mod);

if (argc < 3) {
fprintf(stderr, "usage: %s x y\n", argv[0]);
exit(EXIT_FAILURE);
}

LLVMLinkInJIT();
LLVMInitializeNativeTarget();
LLVMExecutionEngineRef engine;
char *error = NULL;
if (LLVMCreateExecutionEngineForModule(&engine, mod, &error) != 0) {
fprintf(stderr, "failed to create execution engine\n");
abort();
}
if (argc < 3) {
fprintf(stderr, "usage: %s x y\n", argv[0]);
exit(EXIT_FAILURE);
}
long long x = strtoll(argv[1], NULL, 10);
long long y = strtoll(argv[2], NULL, 10);

LLVMGenericValueRef args[] = {
LLVMCreateGenericValueOfInt(LLVMInt32Type(), x, 0),
LLVMCreateGenericValueOfInt(LLVMInt32Type(), y, 0)
};
LLVMGenericValueRef res = LLVMRunFunction(engine, sum, ArraySize(args), args);
printf("%d\n", (int)LLVMGenericValueToInt(res, 0));
LLVMDisposeExecutionEngine(engine);
}