Windows 下的 LLVM 前端项目构建指北

Windows 下的 LLVM 前端项目构建指北

前言

我为什么要写这篇文章

最近因为有编译原理大作业 + 很久以前就有的兴趣,选择了 LLVM IR 作为我们的编译器的中间表示。

当然,LLVM 这个项目的文档是非常良心的,名为万花镜的 Tutorial 也给了我很大的帮助,但是——

在配环境的过程中踩过了无数个坑

这篇文章是我希望我当初在学习 LLVM 时能够看到的东西,也希望能对其他人(包括以后的我)有一定的帮助

下面是正文

Windows 下的 LLVM 环境配置

最直接的问题是,在哪里可以获得我们需要的 LLVM 环境,那么:

官方没有提供 LLVM 环境的二进制包

官方提供的二进制文件是 clang 的二进制安装包 ,虽然那个安装包的图标和名字都是 LLVM 6.0.0

用那个安装器安装后得到的是 LLVM 的一个子集,其中仅仅包括了 clang 需要的工具和库。

正确的做法是下载官方提供的源码,然后自行编译。 LLVM 项目极大的依赖了 cmake 作为通用的构建工具,源码的解压目录下会保存整个项目的 CMakeList.txt,然后可以使用 cmake 来生成 Visual Studio 的解决方案和项目。

一种方式是直接使用 Visual Studio 的 gui 来进行项目的构建,而另一个可行的选项(也是我推荐的)是直接在命令行中使用 cmake 来调用 VS 的工具链进行 build ,具体的指令可以在官方的构建教程中找到:

1
cmake --build .

默认的生成结果是 x86 的, 如果需要 x64 的话需要修改构建选项

整个过程耗时在 2 个小时左右,耐心等待

生成的内容包括数十个 LLVM 开头的 .lib 文件,整套 LLVM 工具链,include 文件等

然后用管理员权限启动一个 console 并使用 cmake 安装

1
cmake --build . --target install

根据生成的结果类型会安装在相应的默认目录中,比如 x86 会安装在 C:\Program Files(x86)\LLVM 下

使用 Visual Studio 建立 LLVM 测试工程

因为使用的是 LLVM C++ bindings,直接建立 Visual C++ 空项目。

然后在项目的 属性页 > VC++ 目录 下填好安装完的 LLVM include 和 lib 目录

然后进入 C/C++ > 常规 中把 SDL 检查设置为 否 (这个不设置会导致为 llvm::Module 等类型分配的空间无法被操作系统正确释放,并在 nt.dll 或者 wnt.dll(32位) 中抛出异常)

根据生成的 LLVM 库的类型,有时需要:进入 C/C++ > 代码生成,将运行库改为 多线程(/MT) 来正确链接 .lib 文件

LLVM 的 lib 在 链接器 > 输入 中的 附加依赖项中填写,下面是 LLVM 中所有的 .lib 文件:

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
LLVMAArch64AsmParser.lib
LLVMAArch64AsmPrinter.lib
LLVMAArch64CodeGen.lib
LLVMAArch64Desc.lib
LLVMAArch64Disassembler.lib
LLVMAArch64Info.lib
LLVMAArch64Utils.lib
LLVMAMDGPUAsmParser.lib
LLVMAMDGPUAsmPrinter.lib
LLVMAMDGPUCodeGen.lib
LLVMAMDGPUDesc.lib
LLVMAMDGPUDisassembler.lib
LLVMAMDGPUInfo.lib
LLVMAMDGPUUtils.lib
LLVMARMAsmParser.lib
LLVMARMAsmPrinter.lib
LLVMARMCodeGen.lib
LLVMARMDesc.lib
LLVMARMDisassembler.lib
LLVMARMInfo.lib
LLVMARMUtils.lib
LLVMAnalysis.lib
LLVMAsmParser.lib
LLVMAsmPrinter.lib
LLVMBPFAsmPrinter.lib
LLVMBPFCodeGen.lib
LLVMBPFDesc.lib
LLVMBPFDisassembler.lib
LLVMBPFInfo.lib
LLVMBinaryFormat.lib
LLVMBitReader.lib
LLVMBitWriter.lib
LLVMCodeGen.lib
LLVMCore.lib
LLVMCoroutines.lib
LLVMCoverage.lib
LLVMDebugInfoCodeView.lib
LLVMDebugInfoDWARF.lib
LLVMDebugInfoMSF.lib
LLVMDebugInfoPDB.lib
LLVMDemangle.lib
LLVMDlltoolDriver.lib
LLVMExecutionEngine.lib
LLVMGlobalISel.lib
LLVMHexagonAsmParser.lib
LLVMHexagonCodeGen.lib
LLVMHexagonDesc.lib
LLVMHexagonDisassembler.lib
LLVMHexagonInfo.lib
LLVMIRReader.lib
LLVMInstCombine.lib
LLVMInstrumentation.lib
LLVMInterpreter.lib
LLVMLTO.lib
LLVMLanaiAsmParser.lib
LLVMLanaiAsmPrinter.lib
LLVMLanaiCodeGen.lib
LLVMLanaiDesc.lib
LLVMLanaiDisassembler.lib
LLVMLanaiInfo.lib
LLVMLibDriver.lib
LLVMLineEditor.lib
LLVMLinker.lib
LLVMMC.lib
LLVMMCDisassembler.lib
LLVMMCJIT.lib
LLVMMCParser.lib
LLVMMIRParser.lib
LLVMMSP430AsmPrinter.lib
LLVMMSP430CodeGen.lib
LLVMMSP430Desc.lib
LLVMMSP430Info.lib
LLVMMipsAsmParser.lib
LLVMMipsAsmPrinter.lib
LLVMMipsCodeGen.lib
LLVMMipsDesc.lib
LLVMMipsDisassembler.lib
LLVMMipsInfo.lib
LLVMNVPTXAsmPrinter.lib
LLVMNVPTXCodeGen.lib
LLVMNVPTXDesc.lib
LLVMNVPTXInfo.lib
LLVMObjCARCOpts.lib
LLVMObject.lib
LLVMObjectYAML.lib
LLVMOption.lib
LLVMOrcJIT.lib
LLVMPasses.lib
LLVMPowerPCAsmParser.lib
LLVMPowerPCAsmPrinter.lib
LLVMPowerPCCodeGen.lib
LLVMPowerPCDesc.lib
LLVMPowerPCDisassembler.lib
LLVMPowerPCInfo.lib
LLVMProfileData.lib
LLVMRuntimeDyld.lib
LLVMScalarOpts.lib
LLVMSelectionDAG.lib
LLVMSparcAsmParser.lib
LLVMSparcAsmPrinter.lib
LLVMSparcCodeGen.lib
LLVMSparcDesc.lib
LLVMSparcDisassembler.lib
LLVMSparcInfo.lib
LLVMSupport.lib
LLVMSymbolize.lib
LLVMSystemZAsmParser.lib
LLVMSystemZAsmPrinter.lib
LLVMSystemZCodeGen.lib
LLVMSystemZDesc.lib
LLVMSystemZDisassembler.lib
LLVMSystemZInfo.lib
LLVMTableGen.lib
LLVMTarget.lib
LLVMTransformUtils.lib
LLVMVectorize.lib
LLVMWindowsManifest.lib
LLVMX86AsmParser.lib
LLVMX86AsmPrinter.lib
LLVMX86CodeGen.lib
LLVMX86Desc.lib
LLVMX86Disassembler.lib
LLVMX86Info.lib
LLVMX86Utils.lib
LLVMXCoreAsmPrinter.lib
LLVMXCoreCodeGen.lib
LLVMXCoreDesc.lib
LLVMXCoreDisassembler.lib
LLVMXCoreInfo.lib
LLVMXRay.lib
LLVMipo.lib

其中大部分是不同后端需要的代码生成的库, LLVM 的基础设施多数包含在 LLVMCodeGen.lib LLVMCore.lib LLVMSupport.lib 中

下面是一段测试代码:

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
#include "llvm/ADT/APInt.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/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Verifier.h"
#include <memory>
#include <string>
#include <vector>
#include <iostream>

using namespace llvm;

static LLVMContext TheContext;
static IRBuilder<> Builder(TheContext);

void temp()
{
std::unique_ptr<Module> TheModule = std::make_unique<Module>("main module", TheContext);
FunctionType* fun_type =
FunctionType::get(Type::getInt32Ty(TheContext), std::vector<Type*>(2, Type::getInt32Ty(TheContext)), false);
Function* fun = Function::Create(fun_type, GlobalValue::InternalLinkage, "function", TheModule.get());

std::vector<Value*> args;
int cnt = 0;
std::string xx[] = { "a", "b" };
for (auto& arg : fun->args()) { arg.setName(xx[cnt++]); args.push_back(&arg); }

BasicBlock* bb = BasicBlock::Create(TheContext, "entry", fun);
Builder.SetInsertPoint(bb);
Value* constant2 = Constant::getIntegerValue(Type::getInt32Ty(TheContext), APInt(32, 2));
Value* constant48 = Constant::getIntegerValue(Type::getInt32Ty(TheContext), APInt(32, 48));
Value* a = Builder.CreateAdd(constant2, constant48, "w");
FunctionType* putchar_type =
FunctionType::get(Type::getInt32Ty(TheContext), std::vector<Type*>(1, Type::getInt32Ty(TheContext)), false);

Function* fputchar = Function::Create(putchar_type, GlobalValue::ExternalLinkage, "putchar", TheModule.get());
Value* b = Builder.CreateAdd(a, args[0], "x");
Value* c = Builder.CreateAdd(b, args[1], "y");
Value* z = Builder.CreateCall(fputchar, std::vector<Value*>(1, c), "z");
Builder.CreateRet(z);
TheModule->print(outs(), nullptr);
}

int main() {
temp();
int n; std::cin >> n;
return 0;
}

这段测试代码干的事情是:建立全局 LLVM 上下文,建立一个名为 “main module” 的模块,在模块中声明并定义了一个名为 function 的函数,函数中有 一个 BasicBlock ,这个 BasicBlock 的入口标签为 “entry” ,并包含 4 条语句 ,在 Module 的末尾添加上对未定义函数 putchar 的声明

下面是正确运行时应该生成的东西:

1
2
3
4
5
6
7
8
9
10
11
12
; ModuleID = 'main module'
source_filename = "main module"

define internal i32 @function(i32 %a, i32 %b) {
entry:
%x = add i32 50, %a
%y = add i32 %x, %b
%z = call i32 @putchar(i32 %y)
ret i32 %z
}

declare i32 @putchar(i32)