是否可以在 WebAssembly 中使用 Rust 进行动态链接?
Is it possible to do dynamic linking in WebAssembly with Rust?
我正在使用 wasm-bindgen 在 Rust 中为网络制作一个图灵完备的 DSL。我希望能够从网络上下载任意 WASM 代码,然后在我的 DSL 中使用该文件中的函数。我想到的是某种等效于 dlopen
的动态链接。
虽然我不知道如何真正实现这一目标。
通过阅读 WebAssembly docs 我的印象是它确实应该是可能的,但我的知识还不足以理解该文档中的过程细节。
wasm-bindgen 参考中有一个 chapter 详细说明了如何 从 WebAssembly 模块内部实例化 WebAssembly 模块!,但这似乎是通过 JavaScript 这似乎不是最理想的,而不是 WebAssembly 文档描述的内容。
在 js-sys 中,可以从任意字符串创建 JavaScript 函数,但这本质上是从 JavaScript 端调用 Function(/* some arbitrary string */)
,这似乎又是次优的,而不是 WebAssembly文档描述。
是否有可能或有其他更合适的方法来实现我的目标?
llvm/lld 中对 WebAssembly 的动态链接支持是 still a work in progress。我想 Rust 中的动态链接目前在 llvm/lld 中更普遍地被动态链接支持所阻止。
不要在生产代码中使用它(它是纸牌屋),我只是将我的研究成果分享给同样围绕该主题进行修补的其他人。这将允许您在运行时任意更改绑定。对于每个优化级别,今天似乎都能正常工作,但谁知道它明天是否会工作。真正的支持,看sbc100的回答。
/// If at any point you call this function directly, it will probably do exactly what its
/// implementation here is, as it should compile to a "call" instruction when you directly call.
/// That instruction does not appear to be impacted by changes in the function table.
pub fn replace_me() {
// The function body must be unique, otherwise the optimizer will deduplicate
// it and you create unintended impacts. This is never called, it's just unique.
force_call_indirect_for_function_index(replace_me as u32);
}
/// We'll replace the above function with this function for all "call indirect" instructions.
pub fn return_50() -> u64 {
50
}
/// This allows us to force "call indirect". Both no_mangle and inline(never) seem to be required.
/// You could simply strip every invocation of this function from your final wasm binary, since
/// it takes one value and returns one value. It's here to stop optimizations around the function
/// invocation by index.
#[inline(never)]
#[no_mangle]
fn force_call_indirect_for_function_index(function_index: u32) -> u32 {
function_index
}
/// Inline this or make it generic or whatever you want for ease of use, this is your calling code.
/// Note that the function index you use does not need to have the same signature as the function it
/// is replaced with.
///
/// This seems to compile to:
/// i32.const, call force_call_indirect_for_function_index, call indirect.
///
/// So stripping force_call_indirect_for_function_index invocations would make this as efficient
/// as possible for a dynamically linked wasm call I think.
fn call_replace_me_indirectly() -> u64 {
unsafe {
std::mem::transmute::<u32, fn() -> u64>(force_call_indirect_for_function_index(
replace_me as u32,
))()
}
}
/// Replaces replace_me with return_50 in the wasm function table. I've tested that this works with
/// Functions exported from other wasm modules. For this example, I'll use a function defined in
/// this module (return_50).
fn replace_replace_me() {
let function_table: js_sys::WebAssembly::Table = wasm_bindgen::function_table()
.dyn_into::<js_sys::WebAssembly::Table>()
.expect("I'm going to find you...");
let function = function_table
.get(return_50 as u32)
.expect("I know you're in there...");
function_table
.set(replace_me as u32, &function)
.expect("It's not unsafe, but is it undefined behavior?");
}
/// Mangles "replace_me" call indirection invocations, and returns 50.
pub fn watch_me() -> u64 {
replace_replace_me();
call_replace_me_indirectly()
}
我正在使用 wasm-bindgen 在 Rust 中为网络制作一个图灵完备的 DSL。我希望能够从网络上下载任意 WASM 代码,然后在我的 DSL 中使用该文件中的函数。我想到的是某种等效于 dlopen
的动态链接。
虽然我不知道如何真正实现这一目标。
通过阅读 WebAssembly docs 我的印象是它确实应该是可能的,但我的知识还不足以理解该文档中的过程细节。
wasm-bindgen 参考中有一个 chapter 详细说明了如何 从 WebAssembly 模块内部实例化 WebAssembly 模块!,但这似乎是通过 JavaScript 这似乎不是最理想的,而不是 WebAssembly 文档描述的内容。
在 js-sys 中,可以从任意字符串创建 JavaScript 函数,但这本质上是从 JavaScript 端调用 Function(/* some arbitrary string */)
,这似乎又是次优的,而不是 WebAssembly文档描述。
是否有可能或有其他更合适的方法来实现我的目标?
llvm/lld 中对 WebAssembly 的动态链接支持是 still a work in progress。我想 Rust 中的动态链接目前在 llvm/lld 中更普遍地被动态链接支持所阻止。
不要在生产代码中使用它(它是纸牌屋),我只是将我的研究成果分享给同样围绕该主题进行修补的其他人。这将允许您在运行时任意更改绑定。对于每个优化级别,今天似乎都能正常工作,但谁知道它明天是否会工作。真正的支持,看sbc100的回答。
/// If at any point you call this function directly, it will probably do exactly what its
/// implementation here is, as it should compile to a "call" instruction when you directly call.
/// That instruction does not appear to be impacted by changes in the function table.
pub fn replace_me() {
// The function body must be unique, otherwise the optimizer will deduplicate
// it and you create unintended impacts. This is never called, it's just unique.
force_call_indirect_for_function_index(replace_me as u32);
}
/// We'll replace the above function with this function for all "call indirect" instructions.
pub fn return_50() -> u64 {
50
}
/// This allows us to force "call indirect". Both no_mangle and inline(never) seem to be required.
/// You could simply strip every invocation of this function from your final wasm binary, since
/// it takes one value and returns one value. It's here to stop optimizations around the function
/// invocation by index.
#[inline(never)]
#[no_mangle]
fn force_call_indirect_for_function_index(function_index: u32) -> u32 {
function_index
}
/// Inline this or make it generic or whatever you want for ease of use, this is your calling code.
/// Note that the function index you use does not need to have the same signature as the function it
/// is replaced with.
///
/// This seems to compile to:
/// i32.const, call force_call_indirect_for_function_index, call indirect.
///
/// So stripping force_call_indirect_for_function_index invocations would make this as efficient
/// as possible for a dynamically linked wasm call I think.
fn call_replace_me_indirectly() -> u64 {
unsafe {
std::mem::transmute::<u32, fn() -> u64>(force_call_indirect_for_function_index(
replace_me as u32,
))()
}
}
/// Replaces replace_me with return_50 in the wasm function table. I've tested that this works with
/// Functions exported from other wasm modules. For this example, I'll use a function defined in
/// this module (return_50).
fn replace_replace_me() {
let function_table: js_sys::WebAssembly::Table = wasm_bindgen::function_table()
.dyn_into::<js_sys::WebAssembly::Table>()
.expect("I'm going to find you...");
let function = function_table
.get(return_50 as u32)
.expect("I know you're in there...");
function_table
.set(replace_me as u32, &function)
.expect("It's not unsafe, but is it undefined behavior?");
}
/// Mangles "replace_me" call indirection invocations, and returns 50.
pub fn watch_me() -> u64 {
replace_replace_me();
call_replace_me_indirectly()
}