编译到 LLVM 位码

GraalVM 可以执行 C/C++、Rust 和其他可以编译到 LLVM 位码的语言。第一步,您需要使用一些 LLVM 编译器前端将程序编译到 LLVM 位码,例如,使用 clang 编译 C 和 C++,使用 rust 编译 Rust 编程语言等。

文件格式 #

虽然 GraalVM LLVM 运行时可以执行 纯位码文件,但首选格式是包含嵌入式位码原生可执行文件。可执行文件格式在 Linux 和 macOS 上有所不同。Linux 默认使用 ELF 文件。位码存储在名为 .llvmbc 的节中。macOS 平台使用 Mach-O 文件。位码位于 __LLVM 段的 __bundle 节中。

与纯位码文件相比,使用包含嵌入式位码的原生可执行文件有两个优势。首先,原生项目的构建系统(例如 Makefile)期望结果为可执行文件。嵌入位码而不是更改输出格式可以提高与现有项目的兼容性。其次,可执行文件允许指定库依赖关系,而 LLVM 位码则无法做到这一点。GraalVM LLVM 运行时利用此信息来查找和加载依赖关系。

用于编译 C/C++ 的 LLVM 工具链 #

为了简化将 C/C++ 编译到包含嵌入式位码的可执行文件,LLVM 运行时附带了一个预构建的 LLVM 工具链。该工具链包含编译器(例如,用于 C 的 clang 或用于 C++ 的 clang++),以及构建原生项目所需的工具(例如,链接器(ld)或用于创建静态库的归档器(ar))。

  1. 使用 lli--print-toolchain-path 参数获取工具链的位置。
     ./path/to/bin/lli --print-toolchain-path
    
  2. 设置 LLVM_TOOLCHAIN 环境变量。
     export LLVM_TOOLCHAIN=$(./path/to/bin/lli --print-toolchain-path)
    
  3. 然后查看工具链路径的内容,以获取可用工具列表。
     ls $LLVM_TOOLCHAIN
    

就像您进行原生编译一样使用这些工具。例如,将此 C 代码保存到名为 hello.c 的文件中。

#include <stdio.h>

int main() {
    printf("Hello from GraalVM!\n");
    return 0;
}

然后,您可以使用以下命令将 hello.c 编译到包含嵌入式 LLVM 位码的可执行文件。

$LLVM_TOOLCHAIN/clang hello.c -o hello

生成的 hello 可执行文件可以使用 lli 在 GraalVM 上执行。

$JAVA_HOME/bin/lli hello

外部库依赖关系 #

如果位码文件依赖于外部库,GraalVM 会自动从二进制文件头中获取依赖关系。例如。

#include <unistd.h>
#include <ncurses.h>

int main() {
    initscr();
    printw("Hello, Curses!");
    refresh();
    sleep(1);
    endwin();
    return 0;
}

然后,可以编译和运行此 hello-curses.c 文件。

$LLVM_TOOLCHAIN/clang hello-curses.c -lncurses -o hello-curses
lli hello-curses

运行 C++ #

要运行 C++ 代码,GraalVM LLVM 运行时需要 LLVM 项目中的 libc++ 标准库。GraalVM 附带的 LLVM 工具链会自动链接到 libc++。例如,将此代码保存到名为 hello-c++.cpp 的文件中。

#include <iostream>

int main() {
    std::cout << "Hello, C++ World!" << std::endl;
}

使用 GraalVM 附带的 clang++ 编译它并执行。

$LLVM_TOOLCHAIN/clang++ hello-c++.cpp -o hello-c++
lli hello-c++
Hello, C++ World!

运行 Rust #

GraalVM 附带的 LLVM 工具链不包含 Rust 编译器。要安装 Rust,请在命令提示符中运行以下命令,然后按照屏幕上的说明进行操作。

curl https://sh.rustup.rs -sSf | sh

将此示例 Rust 代码保存到名为 hello-rust.rs 的文件中。

fn main() {
    println!("Hello Rust!");
}

然后可以使用 --emit=llvm-bc 标志将其编译到位码。

rustc --emit=llvm-bc hello-rust.rs

要运行 Rust 程序,我们必须告诉 GraalVM 在哪里可以找到 Rust 标准库。

lli --lib $(rustc --print sysroot)/lib/libstd-* hello-rust.bc
Hello Rust!

由于 Rust 编译器没有使用 GraalVM 附带的 LLVM 工具链,因此根据本地 Rust 安装,可能会出现以下错误之一。

Mismatching target triple (expected x86_64-unknown-linux-gnu, got x86_64-pc-linux-gnu)
Mismatching target triple (expected x86_64-apple-macosx10.11.0, got x86_64-apple-darwin)

这表明 Rust 编译器使用的目标三元组与 GraalVM 附带的 LLVM 工具链不同。在本例中,差异仅仅是不同 Linux 发行版或 MacOS 版本的命名约定不同,并没有实质上的差异。在这种情况下,可以安全地忽略错误。

lli --experimental-options --llvm.verifyBitcode=false --lib $(rustc --print sysroot)/lib/libstd-* hello-rust.bc

此选项应仅在手动验证目标三元组确实兼容后使用,即体系结构、操作系统和 C 库都匹配。例如,x86_64-unknown-linux-muslx86_64-unknown-linux-gnu 确实不同,位码是针对不同的 C 库编译的。--llvm.verifyBitcode=false 选项禁用所有检查,GraalVM 然后将尝试运行位码,无论如何,这可能会以不可预知的方式随机失败。

联系我们