Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 1cbf57e

Browse files
committed
refactor dynamic dispatch
1 parent 0a67945 commit 1cbf57e

8 files changed

Lines changed: 138 additions & 63 deletions

File tree

client/executor/common/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ pub enum Error {
8181
/// Execution of a host function failed.
8282
#[display(fmt="Host function {} execution failed with: {}", _0, _1)]
8383
FunctionExecution(String, String),
84+
/// No table is present.
85+
///
86+
/// Call was requested that requires table but none was present in the instance.
87+
NoTable,
88+
/// No table is present.
89+
///
90+
/// Call was requested that requires specific entry in the table to be present.
91+
NoTableEntryWithIndex(u32),
8492
}
8593

8694
impl std::error::Error for Error {

client/executor/common/src/wasm_runtime.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,35 @@
1919
use crate::error::Error;
2020
use sp_wasm_interface::Value;
2121

22+
/// How to locate method to call.
23+
pub enum InvokeLocation<'a> {
24+
/// Call function exported with this name.
25+
///
26+
/// Located function should have (u32, u32) -> u64 signature.
27+
Export(&'a str),
28+
/// Call function by reference from table.
29+
///
30+
/// Located function should have (u32, u32) -> u64 signature.
31+
Table(u32),
32+
/// Call function by reference from table through a wrapper.
33+
///
34+
/// Located function should have (u32, u32, u32) -> u64 signature.
35+
///
36+
/// func will be passed to the
37+
TableWithWrapper {
38+
/// Wrapper for call.
39+
dispatcher_ref: u32,
40+
/// Actual function index that should be invoked.
41+
func: u32,
42+
},
43+
}
44+
45+
impl<'a> From<&'a str> for InvokeLocation<'a> {
46+
fn from(val: &'a str) -> InvokeLocation<'a> {
47+
InvokeLocation::Export(val)
48+
}
49+
}
50+
2251
/// A trait that defines an abstract WASM runtime module.
2352
///
2453
/// This can be implemented by an execution engine.
@@ -31,11 +60,24 @@ pub trait WasmModule: Sync + Send {
3160
///
3261
/// This can be implemented by an execution engine.
3362
pub trait WasmInstance: Send {
34-
/// Call a method on this WASM instance and reset it afterwards.
63+
/// Call a method on this WASM instance.
64+
///
65+
/// Before execution, instance is reset.
66+
///
67+
/// Returns the encoded result on success.
68+
fn call(&self, method: InvokeLocation, data: &[u8]) -> Result<Vec<u8>, Error>;
69+
70+
/// Call an exported method on this WASM instance.
71+
///
72+
/// Before execution, instance is reset.
73+
///
3574
/// Returns the encoded result on success.
36-
fn call(&self, method: &str, data: &[u8]) -> Result<Vec<u8>, Error>;
75+
fn call_export(&self, method: &str, data: &[u8]) -> Result<Vec<u8>, Error> {
76+
self.call(method.into(), data)
77+
}
3778

3879
/// Get the value from a global with the given `name`.
80+
///
3981
/// This method is only suitable for getting immutable globals.
4082
fn get_global_const(&self, name: &str) -> Result<Option<Value>, Error>;
4183
}

client/executor/src/integration_tests/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -554,13 +554,13 @@ fn returns_mutable_static(wasm_method: WasmExecutionMethod) {
554554
).expect("Creates runtime");
555555

556556
let instance = runtime.new_instance().unwrap();
557-
let res = instance.call("returns_mutable_static", &[0]).unwrap();
557+
let res = instance.call_export("returns_mutable_static", &[0]).unwrap();
558558
assert_eq!(33, u64::decode(&mut &res[..]).unwrap());
559559

560560
// We expect that every invocation will need to return the initial
561561
// value plus one. If the value increases more than that then it is
562562
// a sign that the wasm runtime preserves the memory content.
563-
let res = instance.call("returns_mutable_static", &[0]).unwrap();
563+
let res = instance.call_export("returns_mutable_static", &[0]).unwrap();
564564
assert_eq!(33, u64::decode(&mut &res[..]).unwrap());
565565
}
566566

@@ -589,11 +589,11 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) {
589589
let instance = runtime.new_instance().unwrap();
590590

591591
// On the first invocation we allocate approx. 768KB (75%) of stack and then trap.
592-
let res = instance.call("allocates_huge_stack_array", &true.encode());
592+
let res = instance.call_export("allocates_huge_stack_array", &true.encode());
593593
assert!(res.is_err());
594594

595595
// On the second invocation we allocate yet another 768KB (75%) of stack
596-
let res = instance.call("allocates_huge_stack_array", &false.encode());
596+
let res = instance.call_export("allocates_huge_stack_array", &false.encode());
597597
assert!(res.is_ok());
598598
}
599599

@@ -615,10 +615,10 @@ fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) {
615615
.expect("`__heap_base` is an `i32`");
616616

617617
let params = (heap_base as u32, 512u32 * 64 * 1024).encode();
618-
instance.call("check_and_set_in_heap", &params).unwrap();
618+
instance.call_export("check_and_set_in_heap", &params).unwrap();
619619

620620
// Cal it a second time to check that the heap was freed.
621-
instance.call("check_and_set_in_heap", &params).unwrap();
621+
instance.call_export("check_and_set_in_heap", &params).unwrap();
622622
}
623623

624624
#[test_case(WasmExecutionMethod::Interpreted)]

client/executor/src/native_executor.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use sp_core::{
2929
use log::trace;
3030
use std::{result, panic::{UnwindSafe, AssertUnwindSafe}, sync::Arc, collections::HashMap};
3131
use sp_wasm_interface::{HostFunctions, Function};
32-
use sc_executor_common::wasm_runtime::{WasmInstance, WasmModule};
32+
use sc_executor_common::wasm_runtime::{WasmInstance, WasmModule, InvokeLocation};
3333
use sp_externalities::ExternalitiesExt as _;
3434
use sp_io::RuntimeSpawnExt;
3535

@@ -189,7 +189,7 @@ impl sp_core::traits::CallInWasm for WasmExecutor {
189189
&mut **ext,
190190
move || {
191191
RuntimeInstanceSpawn::register_on_externalities(module.clone());
192-
instance.call(method, call_data)
192+
instance.call(InvokeLocation::Export(method), call_data)
193193
}
194194
)
195195
}).map_err(|e| e.to_string())
@@ -214,7 +214,7 @@ impl sp_core::traits::CallInWasm for WasmExecutor {
214214
&mut **ext,
215215
move || {
216216
RuntimeInstanceSpawn::register_on_externalities(module.clone());
217-
instance.call(method, call_data)
217+
instance.call(InvokeLocation::Export(method), call_data)
218218
}
219219
)
220220
.and_then(|r| r)
@@ -295,9 +295,7 @@ pub struct RuntimeInstanceSpawn {
295295
}
296296

297297
impl sp_io::RuntimeSpawn for RuntimeInstanceSpawn {
298-
fn dyn_dispatch(&self, func: u32, data: Vec<u8>) -> u32 {
299-
use codec::Encode as _;
300-
298+
fn dyn_dispatch(&self, dispatcher_ref: u32, func: u32, data: Vec<u8>) -> u32 {
301299
let new_handle = self.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
302300

303301
let (sender, receiver) = std::sync::mpsc::channel();
@@ -317,10 +315,10 @@ impl sp_io::RuntimeSpawn for RuntimeInstanceSpawn {
317315
// pool of istances should be used.
318316
let instance = module.new_instance().expect("Failed to create new instance for fork");
319317

320-
let mut dispatch_data = Vec::new();
321-
func.encode_to(&mut dispatch_data);
322-
data.encode_to(&mut dispatch_data);
323-
instance.call("dyn_dispatch", &dispatch_data[..]).expect("Failed to invoke instance.")
318+
instance.call(
319+
InvokeLocation::TableWithWrapper { dispatcher_ref, func },
320+
&data[..],
321+
).expect("Failed to invoke instance.")
324322
}
325323
);
326324

@@ -430,7 +428,7 @@ impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeExecutor<D> {
430428
&mut **ext,
431429
move || {
432430
RuntimeInstanceSpawn::register_on_externalities(module.clone());
433-
instance.call(method, data).map(NativeOrEncoded::Encoded)
431+
instance.call(InvokeLocation::Export(method), data).map(NativeOrEncoded::Encoded)
434432
}
435433
)
436434
},

client/executor/src/wasm_runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ fn create_versioned_wasm_runtime(
338338
let runtime = AssertUnwindSafe(runtime.as_ref());
339339
crate::native_executor::with_externalities_safe(
340340
&mut **ext,
341-
move || runtime.new_instance()?.call("Core_version", &[])
341+
move || runtime.new_instance()?.call("Core_version".into(), &[])
342342
).map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))?
343343
};
344344
let version = match version_result {

client/executor/wasmi/src/lib.rs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use std::{str, cell::RefCell, sync::Arc};
2020
use wasmi::{
2121
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
22-
memory_units::Pages,
22+
FuncInstance, memory_units::Pages,
2323
RuntimeValue::{I32, I64, self},
2424
};
2525
use codec::{Encode, Decode};
@@ -29,7 +29,7 @@ use sp_wasm_interface::{
2929
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
3030
};
3131
use sp_runtime_interface::unpack_ptr_and_len;
32-
use sc_executor_common::wasm_runtime::{WasmModule, WasmInstance};
32+
use sc_executor_common::wasm_runtime::{WasmModule, WasmInstance, InvokeLocation};
3333
use sc_executor_common::{
3434
error::{Error, WasmError},
3535
sandbox,
@@ -434,7 +434,7 @@ fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
434434
fn call_in_wasm_module(
435435
module_instance: &ModuleRef,
436436
memory: &MemoryRef,
437-
method: &str,
437+
method: InvokeLocation,
438438
data: &[u8],
439439
host_functions: &[&'static dyn Function],
440440
allow_missing_func_imports: bool,
@@ -449,7 +449,7 @@ fn call_in_wasm_module(
449449
let mut fec = FunctionExecutor::new(
450450
memory.clone(),
451451
heap_base,
452-
table,
452+
table.clone(),
453453
host_functions,
454454
allow_missing_func_imports,
455455
missing_functions,
@@ -459,11 +459,36 @@ fn call_in_wasm_module(
459459
let offset = fec.allocate_memory(data.len() as u32)?;
460460
fec.write_memory(offset, data)?;
461461

462-
let result = module_instance.invoke_export(
463-
method,
464-
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
465-
&mut fec,
466-
);
462+
let result = match method {
463+
InvokeLocation::Export(method) => {
464+
module_instance.invoke_export(
465+
method,
466+
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
467+
&mut fec,
468+
)
469+
},
470+
InvokeLocation::Table(func_ref) => {
471+
let func = table.ok_or(Error::NoTable)?
472+
.get(func_ref)?
473+
.ok_or(Error::NoTableEntryWithIndex(func_ref))?;
474+
FuncInstance::invoke(
475+
&func,
476+
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
477+
&mut fec,
478+
).map_err(Into::into)
479+
},
480+
InvokeLocation::TableWithWrapper { dispatcher_ref, func } => {
481+
let dispatcher = table.ok_or(Error::NoTable)?
482+
.get(dispatcher_ref)?
483+
.ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?;
484+
485+
FuncInstance::invoke(
486+
&dispatcher,
487+
&[I32(func as _), I32(u32::from(offset) as i32), I32(data.len() as i32)],
488+
&mut fec,
489+
).map_err(Into::into)
490+
},
491+
};
467492

468493
match result {
469494
Ok(Some(I64(r))) => {
@@ -677,7 +702,7 @@ pub struct WasmiInstance {
677702
unsafe impl Send for WasmiInstance {}
678703

679704
impl WasmInstance for WasmiInstance {
680-
fn call(&self, method: &str, data: &[u8]) -> Result<Vec<u8>, Error> {
705+
fn call(&self, method: InvokeLocation, data: &[u8]) -> Result<Vec<u8>, Error> {
681706
// We reuse a single wasm instance for multiple calls and a previous call (if any)
682707
// altered the state. Therefore, we need to restore the instance to original state.
683708

primitives/io/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ pub trait Sandbox {
11381138
/// Runtime spawn extension.
11391139
pub trait RuntimeSpawn : Send {
11401140
/// Create new runtime instance and use dynamic dispatch to invoke with specified payload.
1141-
fn dyn_dispatch(&self, func: u32, payload: Vec<u8>) -> u32;
1141+
fn dyn_dispatch(&self, dispatcher_ref: u32, func: u32, payload: Vec<u8>) -> u32;
11421142

11431143
/// Join the result of previously created runtime instance invocation.
11441144
fn join(&self, handle: u32) -> Vec<u8>;
@@ -1158,10 +1158,10 @@ pub trait RuntimeTasks {
11581158
/// Wasm host function for spawning task.
11591159
///
11601160
/// This should not be used directly. Use `sp_io::tasks::spawn` instead.
1161-
fn spawn(&mut self, entry: u32, payload: Vec<u8>) -> u32 {
1161+
fn spawn(&mut self, dispatcher_ref: u32, entry: u32, payload: Vec<u8>) -> u32 {
11621162
let runtime_spawn = self.extension::<RuntimeSpawnExt>()
11631163
.expect("Cannot spawn without dynamic runtime dispatcher (RuntimeSpawnExt)");
1164-
runtime_spawn.dyn_dispatch(entry, payload)
1164+
runtime_spawn.dyn_dispatch(dispatcher_ref, entry, payload)
11651165
}
11661166

11671167
/// Wasm host function for joining a task.

primitives/io/src/tasks.rs

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
//! Runtime tasks.
1919
//!
20-
//! Contains runtime-usable interface for spawning parallel purely computational tasks.
20+
//! Contains runtime-usable functions for spawning parallel purely computational tasks.
21+
//!
2122
2223
#[cfg(feature = "std")]
2324
mod inner {
@@ -60,40 +61,41 @@ mod inner {
6061
#[cfg(not(feature = "std"))]
6162
mod inner {
6263

63-
use sp_std::vec::Vec;
64+
use sp_std::{vec::Vec, prelude::Box};
6465

65-
/// Spawn new runtime task (wasm).
66-
pub fn spawn(entry_point: fn(Vec<u8>) -> Vec<u8>, payload: Vec<u8>) -> DataJoinHandle {
6766

68-
/// Dynamic dispatch of wasm blob.
69-
///
70-
/// Arguments are expected to be scale encoded in vector at address `payload_ptr` with length of
71-
/// `payload_len`.
72-
///
73-
/// Arguments: function pointer (u32), input (Vec<u8>).
74-
///
75-
/// Function at pointer is expected to have signature of `(Vec<u8>) -> Vec<u8>`. Since this dynamic dispatch
76-
/// function and the invoked function are compiled with the same compiler, there should be no problem with
77-
/// ABI incompatibility.
78-
#[no_mangle]
79-
unsafe extern "C" fn dyn_dispatch(payload_ptr: u32, payload_len: u32) -> u64 {
80-
81-
use codec::Decode as _;
82-
83-
let mut data: &[u8] = core::slice::from_raw_parts(payload_ptr as usize as *const u8, payload_len as usize);
84-
let entry = u32::decode(&mut data).expect("Failed to decode input") as usize;
85-
let ptr: fn(Vec<u8>) -> Vec<u8> = core::mem::transmute(entry);
86-
let payload = Vec::<u8>::decode(&mut data).expect("Failed to decode input");
87-
88-
let output = (ptr)(payload);
89-
90-
let mut output_encode = (output.len() as u64) << 32;
91-
output_encode | (output.as_ptr() as usize as u64)
92-
}
67+
/// Dynamic dispatch of wasm blob.
68+
///
69+
/// Arguments are expected to be scale encoded in vector at address `payload_ptr` with length of
70+
/// `payload_len`.
71+
///
72+
/// Arguments: function pointer (u32), input (Vec<u8>).
73+
///
74+
/// Function at pointer is expected to have signature of `(Vec<u8>) -> Vec<u8>`. Since this dynamic dispatch
75+
/// function and the invoked function are compiled with the same compiler, there should be no problem with
76+
/// ABI incompatibility.
77+
extern "C" fn dispatch_wrapper(func_ref: u32, payload_ptr: u32, payload_len: u32) -> u64 {
78+
let payload_len = payload_len as usize;
79+
let output = unsafe {
80+
let payload = Vec::from_raw_parts(payload_ptr as usize as *mut _, payload_len, payload_len);
81+
let ptr: fn(Vec<u8>) -> Vec<u8> = core::mem::transmute(func_ref);
82+
(ptr)(payload)
83+
};
84+
let mut output_encode = (output.len() as u64) << 32;
85+
output_encode | (output.as_ptr() as usize as u64)
86+
}
9387

88+
/// Spawn new runtime task (wasm).
89+
pub fn spawn(entry_point: fn(Vec<u8>) -> Vec<u8>, payload: Vec<u8>) -> DataJoinHandle {
9490
let func_ptr: usize = unsafe { core::mem::transmute(entry_point) };
9591

96-
let handle = unsafe { crate::runtime_tasks::spawn(func_ptr as u32, payload) };
92+
let handle = unsafe {
93+
crate::runtime_tasks::spawn(
94+
(dispatch_wrapper as extern "C" fn(u32, u32, u32) -> u64 as usize) as u32,
95+
func_ptr as u32,
96+
payload,
97+
)
98+
};
9799
DataJoinHandle { handle }
98100
}
99101

0 commit comments

Comments
 (0)