编译为 LLVM 位码

GraalVM 可以执行 C/C++、Rust 以及其他可编译为 LLVM 位码的语言。第一步,您必须使用某个 LLVM 编译器前端将程序编译为 LLVM 位码,例如 C 和 C++ 使用 clang,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 将无论如何尝试运行位码,这可能会以意想不到的方式随机失败。

联系我们