#llvm #jit #orc #plugin

nightly no-std bin+lib predicator

predicator 允许使用 LLVM 的 ORC JIT 在线创建、加载和卸载 Rust 和其他语言插件

2 个版本

使用旧的 Rust 2015

0.0.5 2018 年 9 月 25 日
0.0.3 2017 年 6 月 9 日
0.0.2 2017 年 6 月 9 日
0.0.1 2017 年 6 月 9 日

#127 in 无标准库

AGPL-3.0

230KB
5K SLoC

predicator

predicator 是一个 Rust 包,允许您编写可以加载和卸载、并在运行时优化的插件,支持 Rust 和 LLVM JIT 支持的任何其他语言。它非常适合过滤器、安全检查和存在于长期进程中的 10 秒到 100 秒的短期代码片段。

它是线程安全的,但插件目前仅限于 #[no_std] 包。长期计划是添加对 cargo 的支持,并尝试符号解析器。

静态链接程序可以工作,但如果它们使用第三方库(即使是 libc),则需要特别编译以包含这些定义。

迫不及待想要试试吗?

查看 src/main.rs。这是创建和加载插件的全部所需。创建可以是在单独的进程中,甚至是在另一台机器上。

使用 cargo run 在您的机器上演示其工作。您需要 Rust nightly。截至 2017 年 6 月 9 日已测试于 Mac OS X。

编写插件

设置 Cargo.toml

为了生成合适的插件,需要仔细设置 Cargo.toml 中的值。

首先,确保 [profile] 部分包含以下两个条目:-

[profile.dev]
opt-level = 3
debug = true
rpath = false
lto = true
debug-assertions = true
codegen-units = 1
panic = 'abort'

[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'

lto = true 的设置尤为重要。而 panic = abort 的设置并非必需,但由于无法在 LLVM JIT 插件中将 panic 返回 Rust,这使得开发变得稍微容易一些,因为 panic 在开发和调试测试期间会安全地终止。

其次,添加或替换 [lib] 部分,使其与以下内容完全匹配:

crate-type = ["bin"]

编写 LLVM JIT 插件的代码

一个 LLVM JIT 插件需要一些样板代码才能运行。将此代码放在 lib.rs顶部::

#![no_std]
#![feature(lang_items)]
#![feature(libc)]
#![no_main]


extern crate libc;


#[lang = "panic_fmt"]
fn panic_fmt() -> !
{
    loop
	{
	}
}

#[lang = "eh_personality"]
extern fn eh_personality()
{
}

#[allow(unused_variables)]
#[cfg(not(debug_assertions))]
#[no_mangle]
pub extern fn main(argc: isize, argv: *const *const u8) -> isize
{
    0
}

#[cfg(debug_assertions)]
#[no_mangle]
pub extern fn main(argc: isize, argv: *const *const u8) -> isize
{
    test(argc, argv)
}

/// Change this code to run unit tests when compiled in `debug` mode
/// Return a non-zero value to mark a test failure
fn test(argc: isize, argv: *const *const u8) -> isize
{
	0
}

此代码执行以下操作:

  • 告诉 Rust 编译器只使用 core 包(也称为 libcore
    • 这意味着需要(堆)内存分配的任何内容都不可用
      • 没有集合(例如 VecHashSet 等)
      • 没有 BoxRcArc
      • 没有字符串(例如 StringstrCStringCStrOsStringOsStr
      • 没有路径(例如 PathPathBuf
      • 以及其他类似的限制
    • 此外,panic 的代码并不特别有用
  • 告诉 Rust 编译器我们将提供 core 包中缺少的语言项函数
  • 告诉 Rust 编译器我们将使用其工具链的 libc 包版本(在高级场景中并非必需)
  • 告诉 Rust 编译器不要生成用于支持 Rust 的 main() 函数的常规逻辑,因为插件不需要这些
  • 使用 libc 包;如果没有它,插件将无法编译,因为它依赖于如 crt1.o 这样的 libc 启动文件
  • 添加了一个定义 panic_fmt() 的代码,该代码会无限循环;这不是最佳做法(更好的解决方案可能是将内容写入 stderr 或 syslog)
  • 添加了一段用于异常处理的代码,它实际上什么也不做
  • 定义了一个适用于从 libc 调用的 main() 方法,该方法在发布模式下使用时不会做任何事情。这是为了能够编译代码
  • 定义了一个将 C 和 test() 方法桥接的方法
  • 定义了一个存根 test() 函数,您可以用测试代码的逻辑替换它

现在您已经准备好开始了。只需编写正常函数和代码。为了使函数可用,它需要是 pub#[no_mangle]

例如,一个非常简单的函数可能如下所示:

#[no_mangle]
pub fn simple_plugin()
{
}

然后可以通过查找函数 simple_plugin 来将其用作 LLVM JIT 插件:

extern crate predicator;


use ::predicator::llvm::*;


fn main()
{
	// Create a super context
	let super_context = SuperContext::threadLocal();
	
	// There needs to be at least one context per thread
	let jit_context = super_context.newJitContext(NaiveSymbolResolver(0)).expect("Could not create a new JIT context");
	
	// Can also be created from a slice, and from intermediate representation (.ll files)
	let plugins = jit_context.loadPlugins(ModuleSourceCodeType::BitCode, &MemoryBufferCreator::File("/path/to/bitcode/file.bc")).expect("Could not parse bit code into module");
	
	// Note that there is no way to know the correct arity or arguments for the function pointer
	let simple_plugin_function_pointer = plugins.nullaryFunctionPointer::<()>("simple_plugin").expect("Missing function for simple_plugin");
	
	// Execute the function
	unsafe { simple_plugin_function_pointer() };
	
	// Note that once `plugins` is dropped the function pointer is no longer valid
}

函数也可以使用在 core 包中定义的任何内容。请注意,在使用 libc 包中的内容时要小心,因为 LLVM JIT 插件可以与静态链接代码一起使用,因此 libc 函数和全局静态变量可能没有被链接到使用您的插件的应用程序中。在直接运行插件进行测试时,此注意事项不适用。

函数可以接受参数并返回结果,例如这个插件:

pub fn binary_plugin(size: usize, some_array: [u8; 2]) -> u32
{
	17
}

然后可以这样使用:

	let binary_plugin_function_pointer = orcJitStack.binaryFunctionPointer::<u32, usize, [u8; 64]>("binary_plugin").expect("Missing function for binary_plugin");
	
	// Execute the function
	let size = 20;
	let some_array = [3, 9];
	let result = unsafe { binary_plugin_function_pointer(size, some_array) };
	assert!(result == 17, "result wasn't 17");

请注意,您正在跨越相当于 'C' 界限的情况。预测框架无法阻止您不捕获 panic!、传递 Box 值、类型不匹配等。建议您坚持使用非常简单的结构和原始类型。更复杂的结构也可以工作,但一旦出现问题就非常难以调试。您可以使用来自其他 no_std 包的类型,但应避免从它们导入全局(或线程局部)静态变量。在插件代码中定义全局静态和线程局部静态变量应该是没有问题的。

构建

要构建 LLVM JIT 插件,请使用:

cargo rustc --release --target x86_64-unknown-linux-musl -- --emit=llvm-bc

注意:我们需要检查是否需要 -C relocation-model=static

注意:如果您的 Cargo.toml 配置正确,则实际上可能不需要使用 -C lto 开关。

您可以在 cargo 定义的任何目标文件夹中找到输出。如果您已经分叉了这个包,您可以在 .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-HEX_RANDOM_VALUE.bc 中找到它,其中 NAME_OF_CRATE 是您的包名,RANDOM_VALUE-C metadata=RANDOM_VALUE -C extra-filename=-RANDOM_VALUE cargo 在构建过程中传递的随机值。例如路径可能为:.cargo/target/x86_64-unknown-linux-musl/release/deps/experiment_with_ffi-805d16cbb3e10aad.bc`。当 cargo 构建时,会产生包含此路径提示的构建输出:

   Compiling experiment-with-ffi v0.0.0 (file:///Volumes/Source/GitHub/lemonrock/experiment-with-ffi)
     Running `rustc --crate-name experiment_with_ffi src/lib.rs --crate-type bin --emit=dep-info,link -C opt-level=3 -C panic=abort -C lto --emit=llvm-bc -C metadata=24221fe0742db2e8 -C extra-filename=-24221fe0742db2e8 --out-dir /Volumes/Source/GitHub/lemonrock/experiment-with-ffi/.cargo/target/x86_64-unknown-linux-musl/release/deps --target x86_64-unknown-linux-musl -C ar=x86_64-linux-musl-ar -C linker=x86_64-linux-musl-cc -L dependency=/Volumes/Source/GitHub/lemonrock/experiment-with-ffi/.cargo/target/x86_64-unknown-linux-musl/release/deps -L dependency=/Volumes/Source/GitHub/lemonrock/experiment-with-ffi/.cargo/target/release/deps`
    Finished release [optimized] target(s) in 0.25 secs

在这种情况下,NAME_OF_CRATEexperiment-with-ffi,而 RANDOM_VALUE24221fe0742db2e8,如上所述,作为 -C metadata=24221fe0742db2e8 -C extra-filename=-24221fe0742db2e8

替代构建方法

调试模式

要使用调试模式构建,请使用:

cargo rustc --target x86_64-unknown-linux-musl -- --emit=llvm-bc
要检查插件创建的 LLVM IR 代码,请使用:

要检查生成的 LLVM IR,请使用:

cargo rustc --release --target x86_64-unknown-linux-musl -- --emit=llvm-ir

此代码在调试模式下没有构建,因为它变得非常难以阅读。如果您想以调试模式构建它,请在上面的命令中省略 --release 开关。

移除发布模式代码中的冗余信息

默认情况下,由 Rust 编译器生成的 LLVM IR 和位代码,即使在 --release 下生成,也包含一些额外的调试信息。这可以使用 opt 程序移除。

要从IR代码(.ll文件)中删除它,请执行以下操作:

opt -strip-debug -S -o .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.stripped.ll .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.ll

以及从位代码(.bc文件)中:

opt -strip-debug -o .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.stripped.bc .cargo/target/x86_64-unknown-linux-musl/release/deps/NAME_OF_CRATE-RANDOM_VALUE.bc

注意缺少-S开关。

有趣的是,生成的.ll代码有时可能比.bc代码更小。我观察到高达24%。

感谢

感谢https://github.com/jauhien/iron-kaleidoscope#chapter-3-optimizer-and-jit-support教程的帮助

许可

本项目的许可协议为AFGPL-3.0。

依赖项

~3MB
~52K SLoC