Можно ли сделать динамическое связывание в WebAssembly с Rust?
Я делаю полный комплект по Тьюрингу в Rust для веба, используя wasm-bindgen. Мне нужна возможность загружать произвольный код WASM из Интернета, а затем использовать функции из этого файла в моем DSL. Какая-то динамическая связь с эквивалентом dlopen
это то, что я имею в виду.
Я понятия не имею, как на самом деле достичь этого, хотя.
Из прочтения документации по WebAssembly у меня сложилось впечатление, что это действительно должно быть возможно, но я недостаточно осведомлен, чтобы понять детали процесса из этого документа.
В справочнике wasm-bindgen есть глава, в которой подробно описывается, как создавать экземпляры модулей WebAssembly из модулей WebAssembly!, но это, кажется, делает это через JavaScript, который кажется неоптимальным, а не то, что описывает документ WebAssembly.
В js-sys можно создавать функции JavaScript из произвольных строк, но это по сути вызывает Function(/* some arbitrary string */)
со стороны JavaScript, что опять-таки кажется неоптимальным, а не то, что описывает документ WebAssembly.
Возможно ли это или есть какой-то другой, более подходящий способ достижения моей цели?
1 ответ
Поддержка динамического связывания в llvm/lld для WebAssembly все еще находится в стадии разработки. Я полагаю, что динамическое связывание в 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()
}