LLVM 原生镜像后端
原生镜像提供了一个替代后端,它使用 LLVM 中间表示 和 LLVM 编译器 生成原生可执行文件。此 LLVM 后端使用户能够 将目标指向 GraalVM 原生镜像不直接支持的其他体系结构。但是,这种方法会带来一些性能成本。
安装和使用 #
LLVM 后端默认情况下不包含在原生镜像中。要使用它,您需要 从源代码构建 GraalVM,使用以下 mx 命令
mx --dynamicimports /substratevm build
export JAVA_HOME=$(mx --dynamicimports /substratevm graalvm-home)
要启用 LLVM 后端,请将 -H:CompilerBackend=llvm
选项传递给 native-image
命令。
代码生成选项 #
-H:+BitcodeOptimizations
:在 LLVM 位码级别启用积极优化。这属于实验性功能,可能会导致错误。
调试选项 #
-H:TempDirectory=
:指定原生镜像生成的文件将保存在的位置。LLVM 文件保存在此目录下的SVM-<timestamp>/llvm
中。-H:LLVMMaxFunctionsPerBatch=
:指定编译批处理的大小*。将其设置为 1 会单独编译每个函数,设置为 0 会将所有内容编译为单个批处理。-H:DumpLLVMStackMap=
:指定一个文件,用于转储调试信息,包括编译后的函数与相应位码文件名称之间的映射。
关于批处理:LLVM 编译分为四个阶段
- 为每个函数创建 LLVM 位码文件(名为
f0.bc
、f1.bc
等)。 - 将位码文件链接到批处理中(名为
b0.bc
、b1.bc
等)。当指定-H:LLVMMaxFunctionsPerBatch=1
时,此阶段会被跳过。 - 优化批处理(变为
b0o.bc
、b1o.bc
等),然后编译(变为b0.o
、b1.o
等)。 - 将编译后的批处理链接到单个目标文件(
llvm.o
),然后将其链接到最终的可执行文件。
如何使用 LLVM 后端将目标体系结构添加到 GraalVM #
LLVM 后端的一个有趣用例是将目标指向新体系结构,而无需为原生镜像实现全新的后端。目前,实现此目标需要执行以下步骤。
特定于目标的 LLVM 设置 #
在某些情况下,GraalVM 代码必须超越 LLVM 的目标独立性。这些情况最常见于内联汇编代码段,用于实现直接寄存器访问和直接寄存器跳转(用于蹦床),以及有关 LLVM 生成的代码的堆栈帧结构的精度。这代表了为每个新目标设置的不到十几个简单的值。
LLVM 状态点支持 #
虽然 LLVM 后端主要使用 LLVM 的常见且受良好支持的功能,但垃圾收集支持意味着使用状态点内在函数,这是 LLVM 的一项实验性功能。这意味着支持新体系结构需要在 LLVM 中为请求的目标实现状态点。由于大部分状态点逻辑是在位码级别处理的(即,在目标独立阶段),因此这主要是一个发出正确类型的调用以降低状态点内在函数的问题。
目标文件支持 #
使用 Graal 编译器 LLVM 后端创建的程序的数据部分独立于代码生成,代码由 LLVM 处理。这意味着 Graal 编译器需要了解目标体系结构的目标文件重定位,才能将 LLVM 编译的代码链接到 GraalVM 生成的部分。
(请参见 ELFMachine$ELFAArch64Relocations
获取示例)