Kaleidoscope object generation

学习内容

完成对 https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl08.html 的学习。

练习

看懂原网页后,实现同样的功能。

扩展

1 原示例使用了通用的机器模型进行编译,能否实现针对本地机器的更细粒度优化?

2 能否以较低代价实现支持交叉编译?

进展

完成了原示例中的功能。
新增了功能:针对native机器的细粒度优化(打开本地cpu支持的特性)。
新增了基于环境变量的选项控制功能:默认生成object文件后,可以用选项控制保留LLVM-IR中间文件。

实现

实现的两个主要变更:
1
原示例基于通用cpu的特性来生成代码,优化没有充分利用本地cpu的能力。本次实现改为了基于native去探测本地cpu的能力,选择最合适的指令(相当于使用l了-march=native)。具体实现过程使用了llvm/CodeGen/CommandFlags.inc中的getCPUStr函数来探测cpu。但是该inc文件似乎并不是一个稳定的开放接口文件,在工程内多次包含启动时会有冲突,同时其设置内部变量的方法也比较粗暴。实现时通过将其隔离到单个cpp文件来规避了该问题,可能并不是最好的解决方法。

2
添加object文件生成功能后,按照编译器的通常约定,将默认输出从LLVM-IR修改为object文件。同时,为了调试编译器本身的逻辑,查看编译过程中生成的LLVM-IR也是很有帮助的。
为了支持这样的控制逻辑,添加了一个基于环境变量的选项控制框架。实现代码如下。

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
class control_flags
{
template <typename T>
struct flag_item
{
T flag_val;
const char* input_env;
const char* description;
flag_item(T default_val, const char* env, const char* des) :
input_env(env), description(des)
{
const char *env_val = getenv(input_env);
if (env_val != nullptr)
{
stringstream tmp;
tmp << env_val;
tmp >> flag_val;
}
else
flag_val = default_val;
}
flag_item(){}
operator T() {return flag_val;}
};

public:
#define DECL_FLAG(flag_type, flag_name, default_val, input_env, des) \
flag_item<flag_type> flag_name;
#include "flags.def"
#undef DECL_FLAG
control_flags()
{
#define DECL_FLAG(flag_type, flag_name, default_val, input_env, des) \
flag_item<flag_type> flag_name##cons(default_val, input_env, des);\
flag_name = flag_name##cons;
#include "flags.def"
#undef DECL_FLAG
}
}global_flags;

其基本思路很简单,借鉴自golang编译器的实践。从环境变量中获取输入,避开繁琐的输入选项解析。再利用sstream提供的通用类型转换功能,可以完成大多数情况下的flag数值设置。后续只需要按需增加callback函数做输入的合法性检查即可。
在此框架下添加控制选项只需新增如下一行即可。引用flags时,只需将定义一个全局变量 control_flags global_flag,然后引用global_flag.save_temps等名称即可。

1
2
//变量类型,变量名称,变量默认值,用于控制该变量的环境变量名称,变量作用描述
DECL_FLAG(bool, save_temps, false, "save_temps", "keep intermediate files")

扩展问题

1 原示例使用了通用的机器模型进行编译,能否实现针对本地机器的更细粒度优化?
使用llvm提供的机制即可自动探测本地cpu的能力,细节可参考实现1中的描述。

2 能否以较低代价实现支持交叉编译?
llvm框架中交叉编译是默认配置,本地配置只不过一种特化场景。因此,要支持交叉编译非常容易,只需要新增一个入参指定目标代码的三元组TargetTriple(如x86_64-linux-gnu)即可。但是考虑到新增这个特性后,需要一并添加正确性检查,修改optimizer中的逻辑,工作量稍大。在完成主体工作前,可以稍缓一点实现。