Kaleidoscope:Compile to Object Code

translate from:http://llvm.org/docs/tutorial/LangImpl08.html

本章为“LLVM Tutorial”的第八章:将代码编译为目标对象文件。

本章内容比较简单。

8.1 第八章介绍 #

欢迎来到**“通过LLVM实现一个语言”教程的第八章。本章描述了如何将我们Kaleidoscope程序编译为目标文件**。

8.2 选择一个目标 #

LLVM本身支持交叉编译。你可以将项目编译为适用于你当前机器的架构(或者其它架构)的程序,都是很容易完成的。在本教程中,我们将会编译程序为当前机器的架构。

为了指定我们要编译目标的架构,我们使用一个名为“target triple”的字符串。它的形式为 <arch><sub>-<vendor>-<sys>-<abi> (阅读 交叉编译文档)。

举个例子,我们可以通过运行下面这条命令看到当前平台架构:

$ clang --version | grep Target
Target: x86_64-unknown-linux-gnu

因为你的架构和操作系统可能和我的不一样,所以在你的机器上运行该命令可能显示出不同的结果。

幸运地是,我们不需要硬编码一个target triple(目标三元组)来表示当前机器。LLVM提供一个函数:sys::getDefaultTargetTriple ,调用该函数可以得到当前机器的target triple。

auto TargetTriple = sys::getDefaultTargetTriple();

LLVM不要求我们链接所有目标平台。例如,如果我们只是使用JIT,我们就不需要汇编代码生成。同样地,如果我们仅针对某些体系结构,我们可以只链接这些体系结构。

例如,为了生成目标对象代码,我们将初始化所有的目标:

InitializeAllTargetInfos();
InitializeAllTargets();
InitializeAllTargetMCs();
InitializeAllAsmParsers();
InitializeAllAsmPrinters();

我们现在能够使用我们target triple来获得目标平台的Target:

std::string Error;
auto Target = TargetRegistry::lookupTarget(TargetTriple, Error);

// Print an error and exit if we couldn't find the requested target.
// This generally occurs if we've forgotten to initialise the
// TargetRegistry or we have a bogus target triple.
if (!Target) {
  errs() << Error;
  return 1;
}

8.3 目标机器 #

我们还需要一个TargetMachine. 此类提供了我们当前机器的完整机器描述。如果我们想要指定一个特定的特性(例如SSE)或指定的CPU(例如intel的Sandylake),我们现在就可以开始了。

我们可以通过llc,来获得LLVM包含的特性和CPU。例如,我们指定目标架构为x86:

$ llvm-as < /dev/null | llc -march=x86 -mattr=help
Available CPUs for this target:

  amdfam10      - Select the amdfam10 processor.
  athlon        - Select the athlon processor.
  athlon-4      - Select the athlon-4 processor.
  ...

Available features for this target:

  16bit-mode            - 16-bit mode (i8086).
  32bit-mode            - 32-bit mode (80386).
  3dnow                 - Enable 3DNow! instructions.
  3dnowa                - Enable 3DNow! Athlon instructions.
  ...

对于我们的示例,我们将使用没有任何额外特性、选项或者重定位模型的通用CPU。

auto CPU = "generic";
auto Features = "";

TargetOptions opt;
auto RM = Optional<Reloc::Model>();
auto TargetMachine = Target->createTargetMachine(TargetTriple, CPU, Features, opt, RM);

8.4 配置模块 #

我们现在开始准备配置我们的模块,以指定Targetdata layout数据布局。严格来说,这不是必要的,但是 前端性能指南推荐我们这样做。在知道了Target目标和data layout数据布局后,会很大程度上帮助我们提高优化能力。

TheModule->setDataLayout(TargetMachine->createDataLayout());
TheModule->setTargetTriple(TargetTriple);

8.5 Emit Object Code #

我们现在准备生成obj代码! 让我们定义生成的文件位置:

auto Filename = "output.o";
std::error_code EC;
raw_fd_ostream dest(Filename, EC, sys::fs::F_None);

if (EC) {
  errs() << "Could not open file: " << EC.message();
  return 1;
}

最后,我们定义了一个pass来生成obf代码。然后我们运行该pass:

legacy::PassManager pass;
auto FileType = TargetMachine::CGFT_ObjectFile;

if (TargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType)) {
  errs() << "TargetMachine can't emit a file of this type";
  return 1;
}

pass.run(*TheModule);
dest.flush();

8.6 将它们放在一起 #

我们做的工作成功了吗?让我们测试一下!我们需要编译我们的代码,但是注意llvm-config指定的参数与前面的章节是不同的:

$ clang++ -g -O3 toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs all` -o toy

让我们运行它,并定义一个计算平均值的函数。当你输入完成的时候,按 Ctrl-D。

$ ./toy
ready> def average(x y) (x + y) * 0.5;
^D
Wrote output.o

我们生成了一个目标文件!为了测试它是否是生成正确,让我们编写一个简单的程序并且将它与我们的输出相链接。这里是源代码:

#include <iostream>

extern "C" {
    double average(double, double);
}

int main() {
    std::cout << "average of 3.0 and 4.0: " << average(3.0, 4.0) << std::endl;
}

我们将我们的程序链接为output.o并且检查结果是否符合我们的预期:

$ clang++ main.cpp output.o -o main
$ ./main
average of 3.0 and 4.0: 3.5

Full Code Listing #

http://llvm.org/docs/tutorial/LangImpl08.html#full-code-listing