我正在学习 llvm,并希望对我的想法进行概念验证。
基本上,我想拆分我的编译器和我的运行时。编译器会给出一个 .bc 并且运行时会通过 ParseBitcodeFile 加载它并使用 ExecutionEngine 来运行它。这部分正在工作。
现在,为了轻松进行系统调用,我希望能够在我的运行时 C/C++ 函数中实现所有系统调用(文件 io、stdout 打印等)。我的问题是,我如何从我的玩具编译器的代码中调用这些函数,该编译器由 llvm 在不同的步骤中编译,并允许在执行时使用它。
我正在学习 llvm,并希望对我的想法进行概念验证。
基本上,我想拆分我的编译器和我的运行时。编译器会给出一个 .bc 并且运行时会通过 ParseBitcodeFile 加载它并使用 ExecutionEngine 来运行它。这部分正在工作。
现在,为了轻松进行系统调用,我希望能够在我的运行时 C/C++ 函数中实现所有系统调用(文件 io、stdout 打印等)。我的问题是,我如何从我的玩具编译器的代码中调用这些函数,该编译器由 llvm 在不同的步骤中编译,并允许在执行时使用它。
好消息:当使用 JIT 时ExecutionEngine
,这将起作用。当 JIT-er 发现 IR 使用的外部符号在 IR 本身中找不到时,它会在 JIT-ing 进程本身中查找,因此可以调用主机程序中可见的任何符号。
这在 LLVM 教程的第 4 部分中直接解释:
哇,JIT 怎么知道 sin 和 cos?答案出奇地简单:在这个例子中,JIT 开始执行一个函数并得到一个函数调用。它意识到该函数尚未 JIT 编译并调用标准例程集来解析该函数。在这种情况下,没有为函数定义主体,因此 JIT 最终在 Kaleidoscope 进程本身上调用了“dlsym("sin")”。由于“sin”是在 JIT 的地址空间中定义的,它只是简单地修补模块中的调用以直接调用 libm 版本的 sin。
有关血腥细节,请查看lib/ExecutionEngine/JIT/JIT.cpp
- 特别是DynamicLibrary
.
Eli的回答很好,你应该接受它。不过,还有另一种选择,即单独将运行时的源文件编译为 LLVM 模块(例如使用 Clang)并用于ExecutionEngine::addModule()
添加它们。
它不太方便,它意味着两次编译相同的文件(一次用于您的主机程序,另一个用于Module
从它们获取 s),但优点是它可以从您的 JITted 代码中实现内联和其他跨函数优化。