-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
To call an exported function we need to be able to pass arguments to it. I have a two straw man approaches to implement that.
The first one: pass arguments dynamically. That is, every exported function should have a thunk generated. This thunk reads all arguments from the dynamic list of argument values (a-la wasmi's RuntimeValue) and then call into the original function with the whatever ABI it has. Upon calling the exported function, we check that the argument slice matches the signature and then call we call the thunk with the fixed signature (vmctx, args_ptr). This thunk could also convert and write the return value to the specifc pre-allocated location in the callee. Dumb but easy.
The second one. Generate exported functions with the specific ABI, say system_v. Then introduce an argument wrapper trait.
unsafe trait ArgsWrapper {
fn signature() → WasmSignature;
fn call(fn_ptr: usize, vmctx: usize, args: Self);
}This trait's main purpose is to provide a way to get the signature dynamically and unpack the values for the actual call. We can generate impls for this trait by a macro. Here is an example impl for function with arity 2.
unsafe impl<A: HasWasmType, B: HasWasmType> ArgsWrapper for (A, B) {
fn signature() → WasmSignature {
WasmSignature::new(&[A::wasm_type(), B::wasm_type()])
}
unsafe fn call(fn_ptr: usize, vmctx: usize, args: (A, B)) {
let (a1, a2) = args;
let f = fn_ptr as extern "C" fn(usize, A, B);
unsafe {
f(vmctx, a1, a2)
}
}
}Then actual call can be implemented as following:
fn call<A: ArgsWrapper >(func_name: &str, args: A) {
let wasm_func = self.funcs.get(func_name).unwrap();
assert_eq!(wasm_func.signature, A::signature());
unsafe { A::call(wasm_func.ptr, args) }
}I think this approach can be scaled to handle returning values as well.
@sunfishcode what do you think? Am I missing something, do you have better proposals?