Support wasmtime in sandbox

Just supported wasmtime in the sandbox of europa these days, we can execute ink! contracts using JIT and trace the panicking line numbers of the rust code with DWARF for now.

1. The arch of sandbox

The sandbox we are talking here is primitives/sandbox which executes ink! contract in pallet-contracts, it abstracts a WASM layer that can adapt different wasm executors.


#![allow(unused)]
fn main() {
/// Sandbox liner memory
struct Memory { ... }

/// WASM imports
struct EnvironmentDefinitionBuilder<T> { ... }

/// WASM module instance
struct Instance { ... }
}

1.1 Memory

The host functions defined in pallet-contracts use (ptr: u32, len: u32,) as arguments since WASM only can accept integer.

The process is like:

  • (pallet-contract): Alloc memory from the liner memory of WASM
  • (pallet-contract): Passing pointers into wasm interface
  • (wasm-executor): Get porinters and executue functions
  • (wasm-executor): Return result to pallet-contract
  • (pallet-contract): Read result from liner memory through pointers

1.2 Environment

The environment in sandbox includes Host Functions and Memory, both memory and functions could be externed outside WASM module. It sets up the interaction between native machine and wasm module.

1.3 Instance

WASM module with human friendly interfaces comes to instance, it used for invoking functions in the sandbox.

2. Wasmtime

There are not a lot of trouble adapting wasmtime into the sandbox, passing generic types to the host function may be the one could be mentioned.


#![allow(unused)]
fn main() {
pub fn wrap_fn<T>(store: &Store, state: usize, f: usize, sig: FunctionType) -> Func {
	let func = move |_: Caller<'_>, args: &[Val], results: &mut [Val]| {
		let mut inner_args = vec![];
		for arg in args {
			if let Some(arg) = from_val(arg.clone()) {
				inner_args.push(arg);
			} else {
				return Err(Trap::new("Could not wrap host function"));
			}
		}

		// HACK the LIFETIME
		//
		// # Safety
		//
		// Runtime only run for one call.
		let state: &mut T = unsafe { mem::transmute(state) };
		let func: HostFuncType<T> = unsafe { mem::transmute(f) };
		match func(state, &inner_args) {
			Ok(ret) => {
				if let Some(ret) = from_ret_val(ret) {
					results[0] = ret;
				}
				Ok(())
			}
			Err(_) => Err(Trap::new("Could not wrap host function")),
		}
	};
	Func::new(store, wasmtime_sig(sig), func)
}
}

Likewise, we pass pointers to the Func struct of wasmtime with unsafe code that we can define our host functions.

3. DWARF

The support of DWARF in wasm is still in an early stage for now, which hasn't been written into the SPEC, but an document DWARF for WebAssembly has introduced it.

As a result, we use the DWARF implementation in wasmtime directly now, it could be enable with envrionment WASM_BACKTRACE_DETAILS=1 as how it works in wasmtime.