mirror of
https://github.com/fluencelabs/parity-wasm
synced 2025-06-15 07:51:46 +00:00
Use Externals mechanism instead of HostState
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
extern crate parity_wasm;
|
||||
|
||||
use std::env::args;
|
||||
use parity_wasm::interpreter::{ModuleInstance, HostState};
|
||||
use parity_wasm::interpreter::{ModuleInstance, EmptyExternals};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<_> = args().collect();
|
||||
@ -23,12 +23,12 @@ fn main() {
|
||||
// - "main" module doesn't import native module(s) this is why we don't need to provide external native modules here
|
||||
// This test shows how to implement native module https://github.com/NikVolf/parity-wasm/blob/master/src/interpreter/tests/basics.rs#L197
|
||||
let main = ModuleInstance::new(&module)
|
||||
.run_start(&mut HostState::default())
|
||||
.run_start(&mut EmptyExternals)
|
||||
.expect("Failed to initialize module");
|
||||
|
||||
// The argument should be parsable as a valid integer
|
||||
let argument: i32 = args[2].parse().expect("Integer argument required");
|
||||
|
||||
// "_call" export of function to be executed with an i32 argument and prints the result of execution
|
||||
println!("Result: {:?}", main.invoke_export("_call", &[parity_wasm::RuntimeValue::I32(argument)], &mut HostState::default()));
|
||||
println!("Result: {:?}", main.invoke_export("_call", &[parity_wasm::RuntimeValue::I32(argument)], &mut EmptyExternals));
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use std::env::args;
|
||||
|
||||
use parity_wasm::RuntimeValue;
|
||||
use parity_wasm::elements::{Internal, External, Type, FunctionType, ValueType};
|
||||
use parity_wasm::interpreter::{ModuleInstance, HostState};
|
||||
use parity_wasm::interpreter::{ModuleInstance, EmptyExternals};
|
||||
|
||||
|
||||
fn main() {
|
||||
@ -75,8 +75,8 @@ fn main() {
|
||||
// - "main" module doesn't import native module(s) this is why we don't need to provide external native modules here
|
||||
// This test shows how to implement native module https://github.com/NikVolf/parity-wasm/blob/master/src/interpreter/tests/basics.rs#L197
|
||||
let main = ModuleInstance::new(&module)
|
||||
.run_start(&mut HostState::default())
|
||||
.run_start(&mut EmptyExternals)
|
||||
.expect("Failed to initialize module");
|
||||
|
||||
println!("Result: {:?}", main.invoke_export(func_name, &args, &mut HostState::default()).expect(""));
|
||||
println!("Result: {:?}", main.invoke_export(func_name, &args, &mut EmptyExternals).expect(""));
|
||||
}
|
||||
|
@ -3,10 +3,11 @@ extern crate parity_wasm;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use parity_wasm::elements::Module;
|
||||
use parity_wasm::elements::{Module, FunctionType, ValueType, TableType, GlobalType, MemoryType};
|
||||
use parity_wasm::interpreter::{
|
||||
Error as InterpreterError, HostModule, HostModuleBuilder,
|
||||
ModuleInstance, UserError, HostState, StateKey
|
||||
Error as InterpreterError, ModuleInstance, UserError,
|
||||
HostFuncIndex, Externals, RuntimeValue, GlobalInstance, TableInstance, MemoryInstance,
|
||||
TableRef, MemoryRef, GlobalRef, FuncRef, TryInto, ImportResolver, FuncInstance,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -139,54 +140,123 @@ struct Runtime<'a> {
|
||||
game: &'a mut tictactoe::Game,
|
||||
}
|
||||
|
||||
unsafe impl<'a> StateKey for Runtime<'a> {
|
||||
type Static = Runtime<'static>;
|
||||
const SET_FUNC_INDEX: HostFuncIndex = 0;
|
||||
const GET_FUNC_INDEX: HostFuncIndex = 1;
|
||||
|
||||
impl<'a> Externals for Runtime<'a> {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: HostFuncIndex,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<Option<RuntimeValue>, InterpreterError> {
|
||||
match index {
|
||||
SET_FUNC_INDEX => {
|
||||
let idx: i32 = args[0].try_into().unwrap();
|
||||
self.game.set(idx, self.player)?;
|
||||
Ok(None)
|
||||
}
|
||||
GET_FUNC_INDEX => {
|
||||
let idx: i32 = args[0].try_into().unwrap();
|
||||
let val: i32 = tictactoe::Player::into_i32(self.game.get(idx)?);
|
||||
Ok(Some(val.into()))
|
||||
}
|
||||
_ => panic!("unknown function index")
|
||||
}
|
||||
}
|
||||
|
||||
fn check_signature(&self, index: HostFuncIndex, sig: &FunctionType) -> bool {
|
||||
match index {
|
||||
SET_FUNC_INDEX => {
|
||||
sig.params() == &[ValueType::I32] && sig.return_type() == None
|
||||
}
|
||||
GET_FUNC_INDEX => {
|
||||
sig.params() == &[ValueType::I32] && sig.return_type() == Some(ValueType::I32)
|
||||
}
|
||||
_ => panic!("unknown function index")
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_by_index(&self, _index: usize) -> &MemoryInstance {
|
||||
panic!("host module doesn't export any memories")
|
||||
}
|
||||
|
||||
fn table_by_index(&self, _index: usize) -> &TableInstance {
|
||||
panic!("host module doesn't export any tables")
|
||||
}
|
||||
|
||||
fn global_by_index(&self, _index: usize) -> &GlobalInstance {
|
||||
panic!("host module doesn't export any globals")
|
||||
}
|
||||
}
|
||||
|
||||
struct RuntimeImportResolver;
|
||||
|
||||
impl<'a> ImportResolver for RuntimeImportResolver {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
_func_type: &FunctionType,
|
||||
) -> Result<FuncRef, InterpreterError> {
|
||||
let func_ref = match field_name {
|
||||
"set" => {
|
||||
FuncInstance::alloc_host(FunctionType::new(vec![ValueType::I32], None), SET_FUNC_INDEX)
|
||||
},
|
||||
"get" => FuncInstance::alloc_host(FunctionType::new(vec![ValueType::I32], Some(ValueType::I32)), GET_FUNC_INDEX),
|
||||
_ => return Err(
|
||||
InterpreterError::Function(
|
||||
format!("host module doesn't export function with name {}", field_name)
|
||||
)
|
||||
)
|
||||
};
|
||||
Ok(func_ref)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
&self,
|
||||
_field_name: &str,
|
||||
_global_type: &GlobalType,
|
||||
) -> Result<GlobalRef, InterpreterError> {
|
||||
Err(
|
||||
InterpreterError::Global("host module doesn't export any globals".to_owned())
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
_field_name: &str,
|
||||
_memory_type: &MemoryType,
|
||||
) -> Result<MemoryRef, InterpreterError> {
|
||||
Err(
|
||||
InterpreterError::Global("host module doesn't export any memories".to_owned())
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
&self,
|
||||
_field_name: &str,
|
||||
_table_type: &TableType,
|
||||
) -> Result<TableRef, InterpreterError> {
|
||||
Err(
|
||||
InterpreterError::Global("host module doesn't export any tables".to_owned())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
module: &Module,
|
||||
env: &HostModule,
|
||||
) -> Result<Rc<ModuleInstance>, Error> {
|
||||
let instance = ModuleInstance::new(module)
|
||||
.with_import("env", &*env)
|
||||
.run_start(&mut HostState::default())?;
|
||||
.with_import("env", &RuntimeImportResolver)
|
||||
.assert_no_start()?;
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
fn env_host_module() -> HostModule {
|
||||
HostModuleBuilder::new()
|
||||
.with_func1(
|
||||
"set",
|
||||
|state: &mut HostState, idx: i32| -> Result<(), InterpreterError> {
|
||||
state.with_mut(move |runtime: &mut Runtime| -> Result<(), InterpreterError> {
|
||||
runtime.game.set(idx, runtime.player)?;
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
)
|
||||
.with_func1(
|
||||
"get",
|
||||
|state: &mut HostState, idx: i32| -> Result<i32, InterpreterError> {
|
||||
state.with(move |runtime: &Runtime| -> Result<i32, InterpreterError> {
|
||||
let val: i32 = tictactoe::Player::into_i32(runtime.game.get(idx)?);
|
||||
Ok(val)
|
||||
})
|
||||
},
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn play<'a>(
|
||||
x_module: &Module,
|
||||
o_module: &Module,
|
||||
host_module: &HostModule,
|
||||
game: &'a mut tictactoe::Game,
|
||||
fn play(
|
||||
x_instance: Rc<ModuleInstance>,
|
||||
o_instance: Rc<ModuleInstance>,
|
||||
game: &mut tictactoe::Game,
|
||||
) -> Result<tictactoe::GameResult, Error> {
|
||||
// Instantiate modules of X and O players.
|
||||
let x_instance = instantiate(x_module, host_module)?;
|
||||
let o_instance = instantiate(o_module, host_module)?;
|
||||
|
||||
let mut turn_of = tictactoe::Player::X;
|
||||
let game_result = loop {
|
||||
let (instance, next_turn_of) = match turn_of {
|
||||
@ -199,11 +269,7 @@ fn play<'a>(
|
||||
player: turn_of,
|
||||
game: game,
|
||||
};
|
||||
{
|
||||
let mut host_state = HostState::new();
|
||||
host_state.insert::<Runtime>(&mut runtime);
|
||||
let _ = instance.invoke_export("mk_turn", &[], &mut host_state)?;
|
||||
}
|
||||
let _ = instance.invoke_export("mk_turn", &[], &mut runtime)?;
|
||||
}
|
||||
|
||||
match game.game_result() {
|
||||
@ -219,7 +285,6 @@ fn play<'a>(
|
||||
|
||||
fn main() {
|
||||
let mut game = tictactoe::Game::new();
|
||||
let env_host_module = env_host_module();
|
||||
|
||||
let args: Vec<_> = env::args().collect();
|
||||
if args.len() < 3 {
|
||||
@ -229,6 +294,10 @@ fn main() {
|
||||
let x_module = parity_wasm::deserialize_file(&args[1]).expect("X player module to load");
|
||||
let o_module = parity_wasm::deserialize_file(&args[2]).expect("Y player module to load");
|
||||
|
||||
let result = play(&x_module, &o_module, &env_host_module, &mut game);
|
||||
// Instantiate modules of X and O players.
|
||||
let x_instance = instantiate(&x_module).unwrap();
|
||||
let o_instance = instantiate(&o_module).unwrap();
|
||||
|
||||
let result = play(x_instance, o_instance, &mut game);
|
||||
println!("result = {:?}, game = {:#?}", result, game);
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ use std::collections::HashMap;
|
||||
use std::borrow::Cow;
|
||||
use elements::{FunctionType, Local, Opcodes};
|
||||
use interpreter::{Error, ModuleInstance};
|
||||
use interpreter::host::{Externals, HostFuncIndex};
|
||||
use interpreter::runner::{prepare_function_args, FunctionContext, Interpreter};
|
||||
use interpreter::host::HostFunc;
|
||||
use interpreter::value::RuntimeValue;
|
||||
use interpreter::state::HostState;
|
||||
use common::stack::StackWithLimit;
|
||||
use common::{DEFAULT_FRAME_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT};
|
||||
|
||||
@ -29,8 +28,8 @@ pub enum FuncInstance {
|
||||
body: Rc<FuncBody>,
|
||||
},
|
||||
Host {
|
||||
func_type: Rc<FunctionType>,
|
||||
host_func: Rc<HostFunc>,
|
||||
func_type: FunctionType,
|
||||
host_func: HostFuncIndex,
|
||||
},
|
||||
}
|
||||
|
||||
@ -57,7 +56,7 @@ impl fmt::Debug for FuncInstance {
|
||||
}
|
||||
|
||||
impl FuncInstance {
|
||||
pub fn alloc_internal(
|
||||
pub(crate) fn alloc_internal(
|
||||
module: Rc<ModuleInstance>,
|
||||
func_type: Rc<FunctionType>,
|
||||
body: FuncBody,
|
||||
@ -70,7 +69,7 @@ impl FuncInstance {
|
||||
FuncRef(Rc::new(func))
|
||||
}
|
||||
|
||||
pub fn alloc_host(func_type: Rc<FunctionType>, host_func: Rc<HostFunc>) -> FuncRef {
|
||||
pub fn alloc_host(func_type: FunctionType, host_func: HostFuncIndex) -> FuncRef {
|
||||
let func = FuncInstance::Host {
|
||||
func_type,
|
||||
host_func,
|
||||
@ -78,10 +77,10 @@ impl FuncInstance {
|
||||
FuncRef(Rc::new(func))
|
||||
}
|
||||
|
||||
pub fn func_type(&self) -> Rc<FunctionType> {
|
||||
pub fn func_type(&self) -> &FunctionType {
|
||||
match *self {
|
||||
FuncInstance::Internal { ref func_type, .. } |
|
||||
FuncInstance::Host { ref func_type, .. } => Rc::clone(func_type),
|
||||
FuncInstance::Internal { ref func_type, .. } => func_type,
|
||||
FuncInstance::Host { ref func_type, .. } => func_type,
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,14 +91,14 @@ impl FuncInstance {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke<'a, 'b: 'a>(
|
||||
pub fn invoke<E: Externals>(
|
||||
func: FuncRef,
|
||||
args: Cow<[RuntimeValue]>,
|
||||
state: &'a mut HostState<'b>,
|
||||
externals: &mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
enum InvokeKind<'a> {
|
||||
Internal(FunctionContext),
|
||||
Host(Rc<HostFunc>, &'a [RuntimeValue]),
|
||||
Host(HostFuncIndex, &'a [RuntimeValue]),
|
||||
}
|
||||
|
||||
let result = match *func {
|
||||
@ -117,16 +116,16 @@ impl FuncInstance {
|
||||
InvokeKind::Internal(context)
|
||||
}
|
||||
FuncInstance::Host { ref host_func, .. } => {
|
||||
InvokeKind::Host(Rc::clone(host_func), &*args)
|
||||
InvokeKind::Host(*host_func, &*args)
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
InvokeKind::Internal(ctx) => {
|
||||
let mut interpreter = Interpreter::new(state);
|
||||
let mut interpreter = Interpreter::new(externals);
|
||||
interpreter.run_function(ctx)
|
||||
}
|
||||
InvokeKind::Host(host_func, args) => host_func(state, args),
|
||||
InvokeKind::Host(host_func, args) => externals.invoke_index(host_func, args),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,405 +1,50 @@
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use elements::{FunctionType, GlobalType, MemoryType, TableType, ValueType};
|
||||
use interpreter::module::{ExternVal, ModuleInstance};
|
||||
use interpreter::func::FuncRef;
|
||||
use interpreter::global::GlobalRef;
|
||||
use interpreter::memory::MemoryRef;
|
||||
use interpreter::table::TableRef;
|
||||
use interpreter::func::FuncInstance;
|
||||
use elements::FunctionType;
|
||||
use interpreter::global::GlobalInstance;
|
||||
use interpreter::memory::MemoryInstance;
|
||||
use interpreter::table::TableInstance;
|
||||
use interpreter::value::{RuntimeValue, TryInto};
|
||||
use interpreter::value::RuntimeValue;
|
||||
use interpreter::Error;
|
||||
use interpreter::ImportResolver;
|
||||
use interpreter::state::HostState;
|
||||
|
||||
pub type HostFunc = Fn(&mut HostState, &[RuntimeValue])
|
||||
-> Result<Option<RuntimeValue>, Error>;
|
||||
pub type HostFuncIndex = u32;
|
||||
|
||||
pub struct HostModuleBuilder {
|
||||
exports: HashMap<String, ExternVal>,
|
||||
}
|
||||
|
||||
impl HostModuleBuilder {
|
||||
pub fn new() -> Self {
|
||||
HostModuleBuilder {
|
||||
exports: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_func0<
|
||||
Cl: Fn(&mut HostState) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
&mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) {
|
||||
let func_type = FunctionType::new(vec![], Ret::value_type());
|
||||
let host_func = Rc::new(
|
||||
move |state: &mut HostState, args: &[RuntimeValue]| -> Result<Option<RuntimeValue>, Error> {
|
||||
assert!(args.len() == 0);
|
||||
let result = f(state)?.into_return_val();
|
||||
Ok(result)
|
||||
},
|
||||
);
|
||||
|
||||
let func = FuncInstance::alloc_host(Rc::new(func_type), host_func);
|
||||
self.insert_func(name, func);
|
||||
}
|
||||
|
||||
pub fn insert_func1<
|
||||
Cl: Fn(&mut HostState, P1) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
P1: FromArg + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
&mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) {
|
||||
let func_type = FunctionType::new(vec![P1::value_type()], Ret::value_type());
|
||||
let host_func = Rc::new(
|
||||
move |state: &mut HostState, args: &[RuntimeValue]| -> Result<Option<RuntimeValue>, Error> {
|
||||
assert!(args.len() == 1);
|
||||
let mut args = args.into_iter();
|
||||
let result = f(
|
||||
state,
|
||||
P1::from_arg(args.next().unwrap())
|
||||
)?.into_return_val();
|
||||
Ok(result)
|
||||
},
|
||||
);
|
||||
|
||||
let func = FuncInstance::alloc_host(Rc::new(func_type), host_func);
|
||||
self.insert_func(name, func);
|
||||
}
|
||||
|
||||
pub fn insert_func2<
|
||||
Cl: Fn(&mut HostState, P1, P2) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
P1: FromArg + 'static,
|
||||
P2: FromArg + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
&mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) {
|
||||
let func_type =
|
||||
FunctionType::new(vec![P1::value_type(), P2::value_type()], Ret::value_type());
|
||||
let host_func = Rc::new(
|
||||
move |state: &mut HostState, args: &[RuntimeValue]| -> Result<Option<RuntimeValue>, Error> {
|
||||
assert!(args.len() == 2);
|
||||
let mut args = args.into_iter();
|
||||
let result = f(
|
||||
state,
|
||||
P1::from_arg(args.next().unwrap()),
|
||||
P2::from_arg(args.next().unwrap()),
|
||||
)?.into_return_val();
|
||||
Ok(result)
|
||||
},
|
||||
);
|
||||
|
||||
let func = FuncInstance::alloc_host(Rc::new(func_type), host_func);
|
||||
self.insert_func(name, func);
|
||||
}
|
||||
|
||||
pub fn insert_func3<
|
||||
Cl: Fn(&mut HostState, P1, P2, P3) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
P1: FromArg + 'static,
|
||||
P2: FromArg + 'static,
|
||||
P3: FromArg + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
&mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) {
|
||||
let func_type = FunctionType::new(
|
||||
vec![P1::value_type(), P2::value_type(), P3::value_type()],
|
||||
Ret::value_type(),
|
||||
);
|
||||
let host_func = Rc::new(
|
||||
move |state: &mut HostState, args: &[RuntimeValue]| -> Result<Option<RuntimeValue>, Error> {
|
||||
assert!(args.len() == 3);
|
||||
let mut args = args.into_iter();
|
||||
let result = f(
|
||||
state,
|
||||
P1::from_arg(args.next().unwrap()),
|
||||
P2::from_arg(args.next().unwrap()),
|
||||
P3::from_arg(args.next().unwrap()),
|
||||
)?.into_return_val();
|
||||
Ok(result)
|
||||
},
|
||||
);
|
||||
|
||||
let func = FuncInstance::alloc_host(Rc::new(func_type), host_func);
|
||||
self.insert_func(name, func);
|
||||
}
|
||||
|
||||
pub fn insert_func4<
|
||||
Cl: Fn(&mut HostState, P1, P2, P3, P4) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
P1: FromArg + 'static,
|
||||
P2: FromArg + 'static,
|
||||
P3: FromArg + 'static,
|
||||
P4: FromArg + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
&mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) {
|
||||
let func_type = FunctionType::new(
|
||||
vec![
|
||||
P1::value_type(),
|
||||
P2::value_type(),
|
||||
P3::value_type(),
|
||||
P4::value_type(),
|
||||
],
|
||||
Ret::value_type(),
|
||||
);
|
||||
let host_func = Rc::new(
|
||||
move |state: &mut HostState, args: &[RuntimeValue]| -> Result<Option<RuntimeValue>, Error> {
|
||||
assert!(args.len() == 4);
|
||||
let mut args = args.into_iter();
|
||||
let result = f(
|
||||
state,
|
||||
P1::from_arg(args.next().unwrap()),
|
||||
P2::from_arg(args.next().unwrap()),
|
||||
P3::from_arg(args.next().unwrap()),
|
||||
P4::from_arg(args.next().unwrap()),
|
||||
)?.into_return_val();
|
||||
Ok(result)
|
||||
},
|
||||
);
|
||||
|
||||
let func = FuncInstance::alloc_host(Rc::new(func_type), host_func);
|
||||
self.insert_func(name, func);
|
||||
}
|
||||
|
||||
pub fn with_func0<
|
||||
Cl: Fn(&mut HostState) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) -> Self {
|
||||
self.insert_func0(name, f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_func1<
|
||||
Cl: Fn(&mut HostState, P1) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
P1: FromArg + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) -> Self {
|
||||
self.insert_func1(name, f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_func2<
|
||||
Cl: Fn(&mut HostState, P1, P2) -> Result<Ret, Error> + 'static,
|
||||
Ret: IntoReturnVal + 'static,
|
||||
P1: FromArg + 'static,
|
||||
P2: FromArg + 'static,
|
||||
N: Into<String>,
|
||||
>(
|
||||
mut self,
|
||||
name: N,
|
||||
f: Cl,
|
||||
) -> Self {
|
||||
self.insert_func2(name, f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insert_func<N: Into<String>>(&mut self, name: N, func: FuncRef) {
|
||||
self.insert(name, ExternVal::Func(func));
|
||||
}
|
||||
|
||||
pub fn insert_global<N: Into<String>>(&mut self, name: N, global: GlobalRef) {
|
||||
self.insert(name, ExternVal::Global(global));
|
||||
}
|
||||
|
||||
pub fn insert_memory<N: Into<String>>(&mut self, name: N, memory: MemoryRef) {
|
||||
self.insert(name, ExternVal::Memory(memory));
|
||||
}
|
||||
|
||||
pub fn insert_table<N: Into<String>>(&mut self, name: N, table: TableRef) {
|
||||
self.insert(name, ExternVal::Table(table));
|
||||
}
|
||||
|
||||
pub fn with_global<N: Into<String>>(mut self, name: N, global: GlobalRef) -> Self {
|
||||
self.insert_global(name, global);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_memory<N: Into<String>>(mut self, name: N, memory: MemoryRef) -> Self {
|
||||
self.insert_memory(name, memory);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_table<N: Into<String>>(mut self, name: N, table: TableRef) -> Self {
|
||||
self.insert_table(name, table);
|
||||
self
|
||||
}
|
||||
|
||||
fn insert<N: Into<String>>(&mut self, name: N, extern_val: ExternVal) {
|
||||
match self.exports.entry(name.into()) {
|
||||
Entry::Vacant(v) => v.insert(extern_val),
|
||||
Entry::Occupied(o) => panic!("Duplicate export name {}", o.key()),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn build(self) -> HostModule {
|
||||
let internal_instance = Rc::new(ModuleInstance::with_exports(self.exports));
|
||||
HostModule { internal_instance }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HostModule {
|
||||
internal_instance: Rc<ModuleInstance>,
|
||||
}
|
||||
|
||||
impl HostModule {
|
||||
pub fn export_by_name(&self, name: &str) -> Option<ExternVal> {
|
||||
self.internal_instance.export_by_name(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl ImportResolver for HostModule {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
func_type: &FunctionType,
|
||||
) -> Result<FuncRef, Error> {
|
||||
self.internal_instance.resolve_func(field_name, func_type)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
&self,
|
||||
field_name: &str,
|
||||
global_type: &GlobalType,
|
||||
) -> Result<GlobalRef, Error> {
|
||||
self.internal_instance
|
||||
.resolve_global(field_name, global_type)
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
field_name: &str,
|
||||
memory_type: &MemoryType,
|
||||
) -> Result<MemoryRef, Error> {
|
||||
self.internal_instance
|
||||
.resolve_memory(field_name, memory_type)
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
&self,
|
||||
field_name: &str,
|
||||
table_type: &TableType,
|
||||
) -> Result<TableRef, Error> {
|
||||
self.internal_instance.resolve_table(field_name, table_type)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromArg
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn from_arg(arg: &RuntimeValue) -> Self;
|
||||
fn value_type() -> ValueType;
|
||||
}
|
||||
|
||||
macro_rules! impl_from_arg {
|
||||
($ty: ident, $val_ty: ident) => {
|
||||
impl FromArg for $ty {
|
||||
fn from_arg(arg: &RuntimeValue) -> Self {
|
||||
arg
|
||||
.try_into()
|
||||
.expect(
|
||||
concat!("Due to validation, arg expected to be ", stringify!($val_ty))
|
||||
)
|
||||
}
|
||||
|
||||
fn value_type() -> ValueType {
|
||||
use self::ValueType::*;
|
||||
$val_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_arg!(i32, I32);
|
||||
impl_from_arg!(u32, I32);
|
||||
impl_from_arg!(i64, I64);
|
||||
impl_from_arg!(u64, I64);
|
||||
impl_from_arg!(f32, F32);
|
||||
impl_from_arg!(f64, F64);
|
||||
|
||||
pub trait IntoReturnVal {
|
||||
fn into_return_val(self) -> Option<RuntimeValue>;
|
||||
fn value_type() -> Option<ValueType>;
|
||||
}
|
||||
|
||||
macro_rules! impl_into_return_val {
|
||||
($ty: ident, $val_ty: ident) => {
|
||||
impl IntoReturnVal for $ty {
|
||||
fn into_return_val(self) -> Option<RuntimeValue> {
|
||||
Some(self.into())
|
||||
}
|
||||
|
||||
fn value_type() -> Option<ValueType> {
|
||||
use self::ValueType::*;
|
||||
Some($val_ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_into_return_val!(i32, I32);
|
||||
impl_into_return_val!(u32, I32);
|
||||
impl_into_return_val!(i64, I64);
|
||||
impl_into_return_val!(u64, I64);
|
||||
impl_into_return_val!(f32, F32);
|
||||
impl_into_return_val!(f64, F64);
|
||||
|
||||
impl IntoReturnVal for () {
|
||||
fn into_return_val(self) -> Option<RuntimeValue> {
|
||||
None
|
||||
}
|
||||
|
||||
fn value_type() -> Option<ValueType> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
trait Externals {
|
||||
pub trait Externals {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: u32,
|
||||
index: HostFuncIndex,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<Option<RuntimeValue>, Error>;
|
||||
|
||||
// TODO: or check signature?
|
||||
fn signature(&self, index: usize) -> &FunctionType;
|
||||
fn check_signature(&self, index: HostFuncIndex, signature: &FunctionType) -> bool;
|
||||
|
||||
fn memory_by_index(&self, index: usize) -> &MemoryInstance;
|
||||
fn table_by_index(&self, index: usize) -> &TableInstance;
|
||||
fn global_by_index(&self, index: usize) -> &GlobalInstance;
|
||||
}
|
||||
|
||||
pub struct EmptyExternals;
|
||||
|
||||
impl Externals for EmptyExternals {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
_index: HostFuncIndex,
|
||||
_args: &[RuntimeValue],
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
panic!("called invoke_index on EmptyExternals")
|
||||
}
|
||||
|
||||
fn check_signature(&self, _index: HostFuncIndex, _signature: &FunctionType) -> bool {
|
||||
panic!("called check_signature on EmptyExternals")
|
||||
}
|
||||
|
||||
fn memory_by_index(&self, _index: usize) -> &MemoryInstance {
|
||||
panic!("called memory_by_index on EmptyExternals")
|
||||
}
|
||||
|
||||
fn table_by_index(&self, _index: usize) -> &TableInstance {
|
||||
panic!("called table_by_index on EmptyExternals")
|
||||
}
|
||||
|
||||
fn global_by_index(&self, _index: usize) -> &GlobalInstance {
|
||||
panic!("called global_by_index on EmptyExternals")
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,6 @@ mod host;
|
||||
mod imports;
|
||||
mod global;
|
||||
mod func;
|
||||
mod state;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@ -154,10 +153,9 @@ mod tests;
|
||||
pub use self::memory::{MemoryInstance, MemoryRef};
|
||||
pub use self::table::{TableInstance, TableRef};
|
||||
pub use self::program::ProgramInstance;
|
||||
pub use self::value::RuntimeValue;
|
||||
pub use self::host::{HostModule, HostModuleBuilder, HostFunc, IntoReturnVal, FromArg};
|
||||
pub use self::value::{RuntimeValue, TryInto};
|
||||
pub use self::host::{Externals, HostFuncIndex, EmptyExternals};
|
||||
pub use self::imports::{ImportResolver, Imports};
|
||||
pub use self::module::ModuleInstance;
|
||||
pub use self::global::{GlobalInstance, GlobalRef};
|
||||
pub use self::func::{FuncInstance, FuncRef};
|
||||
pub use self::state::{HostState, StateKey};
|
||||
|
@ -10,8 +10,8 @@ use interpreter::imports::{ImportResolver, Imports};
|
||||
use interpreter::global::{GlobalInstance, GlobalRef};
|
||||
use interpreter::func::{FuncRef, FuncBody, FuncInstance};
|
||||
use interpreter::table::TableRef;
|
||||
use interpreter::state::HostState;
|
||||
use interpreter::memory::MemoryRef;
|
||||
use interpreter::host::Externals;
|
||||
use validation::validate_module;
|
||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||
|
||||
@ -187,7 +187,7 @@ impl ModuleInstance {
|
||||
"Due to validation function type should exists",
|
||||
);
|
||||
let actual_fn_type = func.func_type();
|
||||
if expected_fn_type != actual_fn_type {
|
||||
if &*expected_fn_type != actual_fn_type {
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Expected function with type {:?}, but actual type is {:?} for entry {}",
|
||||
expected_fn_type,
|
||||
@ -405,11 +405,11 @@ impl ModuleInstance {
|
||||
InstantiationBuilder::new(module)
|
||||
}
|
||||
|
||||
pub fn invoke_index<'a>(
|
||||
pub fn invoke_index<E: Externals>(
|
||||
&self,
|
||||
func_idx: u32,
|
||||
args: &[RuntimeValue],
|
||||
state: &'a mut HostState<'a>,
|
||||
state: &mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
let func_instance = self.func_by_index(func_idx).ok_or_else(|| {
|
||||
Error::Program(format!(
|
||||
@ -420,11 +420,11 @@ impl ModuleInstance {
|
||||
FuncInstance::invoke(func_instance, Cow::Borrowed(args), state)
|
||||
}
|
||||
|
||||
pub fn invoke_export<'a>(
|
||||
pub fn invoke_export<E: Externals>(
|
||||
&self,
|
||||
func_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
state: &'a mut HostState<'a>,
|
||||
state: &mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
let extern_val = self.export_by_name(func_name).ok_or_else(|| {
|
||||
Error::Program(format!("Module doesn't have export {}", func_name))
|
||||
@ -474,7 +474,7 @@ impl<'a> InstantiationBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run_start<'b>(mut self, state: &'b mut HostState<'b>) -> Result<Rc<ModuleInstance>, Error> {
|
||||
pub fn run_start<'b, E: Externals>(mut self, state: &'b mut E) -> Result<Rc<ModuleInstance>, Error> {
|
||||
let imports = self.imports.get_or_insert_with(|| Imports::default());
|
||||
let instance = ModuleInstance::instantiate_with_imports(self.module, imports)?;
|
||||
|
||||
|
@ -5,10 +5,9 @@ use elements::Module;
|
||||
use interpreter::Error;
|
||||
use interpreter::module::{ModuleInstance};
|
||||
use interpreter::func::{FuncInstance, FuncRef};
|
||||
use interpreter::host::HostModule;
|
||||
use interpreter::value::RuntimeValue;
|
||||
use interpreter::imports::{Imports, ImportResolver};
|
||||
use interpreter::state::HostState;
|
||||
use interpreter::host::Externals;
|
||||
|
||||
/// Program instance. Program is a set of instantiated modules.
|
||||
#[deprecated]
|
||||
@ -27,11 +26,11 @@ impl ProgramInstance {
|
||||
}
|
||||
|
||||
/// Instantiate module with validation.
|
||||
pub fn add_module<'a>(
|
||||
pub fn add_module<'a, E: Externals>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
module: Module,
|
||||
state: &'a mut HostState<'a>,
|
||||
externals: &'a mut E,
|
||||
) -> Result<Rc<ModuleInstance>, Error> {
|
||||
let module_instance = {
|
||||
let mut imports = Imports::new();
|
||||
@ -43,7 +42,7 @@ impl ProgramInstance {
|
||||
}
|
||||
ModuleInstance::new(&module)
|
||||
.with_imports(imports)
|
||||
.run_start(state)?
|
||||
.run_start(externals)?
|
||||
};
|
||||
self.modules.insert(name.to_owned(), Rc::clone(&module_instance));
|
||||
|
||||
@ -58,49 +57,41 @@ impl ProgramInstance {
|
||||
self.resolvers.insert(name.to_owned(), import_resolver);
|
||||
}
|
||||
|
||||
pub fn add_host_module(
|
||||
&mut self,
|
||||
name: &str,
|
||||
host_module: HostModule,
|
||||
) {
|
||||
self.resolvers.insert(name.to_owned(), Box::new(host_module) as Box<ImportResolver>);
|
||||
}
|
||||
|
||||
pub fn insert_loaded_module(&mut self, name: &str, module: Rc<ModuleInstance>) {
|
||||
self.modules.insert(name.to_owned(), module);
|
||||
}
|
||||
|
||||
pub fn invoke_export<'a>(
|
||||
pub fn invoke_export<'a, E: Externals>(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
func_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
state: &'a mut HostState<'a>,
|
||||
externals: &'a mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
let module_instance = self.modules.get(module_name).ok_or_else(|| {
|
||||
Error::Program(format!("Module {} not found", module_name))
|
||||
})?;
|
||||
module_instance.invoke_export(func_name, args, state)
|
||||
module_instance.invoke_export(func_name, args, externals)
|
||||
}
|
||||
|
||||
pub fn invoke_index<'a>(
|
||||
pub fn invoke_index<'a, E: Externals>(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
func_idx: u32,
|
||||
args: &[RuntimeValue],
|
||||
state: &'a mut HostState<'a>,
|
||||
externals: &'a mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
let module_instance = self.modules.get(module_name).cloned().ok_or_else(|| {
|
||||
Error::Program(format!("Module {} not found", module_name))
|
||||
})?;
|
||||
module_instance.invoke_index(func_idx, args, state)
|
||||
module_instance.invoke_index(func_idx, args, externals)
|
||||
}
|
||||
|
||||
pub fn invoke_func<'a>(
|
||||
pub fn invoke_func<'a, E: Externals>(
|
||||
&mut self,
|
||||
func_instance: FuncRef,
|
||||
args: &[RuntimeValue],
|
||||
state: &'a mut HostState<'a>,
|
||||
state: &'a mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
FuncInstance::invoke(func_instance.clone(), Cow::Borrowed(args), state)
|
||||
}
|
||||
|
@ -14,13 +14,13 @@ use interpreter::value::{
|
||||
RuntimeValue, TryInto, WrapInto, TryTruncateInto, ExtendInto,
|
||||
ArithmeticOps, Integer, Float, LittleEndianConvert, TransmuteInto,
|
||||
};
|
||||
use interpreter::host::Externals;
|
||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, BlockFrame, BlockFrameType};
|
||||
use common::stack::StackWithLimit;
|
||||
use interpreter::state::HostState;
|
||||
|
||||
/// Function interpreter.
|
||||
pub struct Interpreter<'a, 'b: 'a> {
|
||||
state: &'a mut HostState<'b>,
|
||||
pub struct Interpreter<'a, E: Externals + 'a> {
|
||||
externals: &'a mut E,
|
||||
}
|
||||
|
||||
/// Function execution context.
|
||||
@ -64,10 +64,10 @@ enum RunResult {
|
||||
NestedCall(FuncRef),
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> Interpreter<'a, 'b> {
|
||||
pub fn new(state: &'a mut HostState<'b>) -> Interpreter<'a, 'b> {
|
||||
impl<'a, E: Externals> Interpreter<'a, E> {
|
||||
pub fn new(externals: &'a mut E) -> Interpreter<'a, E> {
|
||||
Interpreter {
|
||||
state: state,
|
||||
externals,
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ impl<'a, 'b: 'a> Interpreter<'a, 'b> {
|
||||
},
|
||||
FuncInstance::Host { ref func_type, .. } => {
|
||||
let args = prepare_function_args(func_type, &mut function_context.value_stack)?;
|
||||
let return_val = FuncInstance::invoke(nested_func.clone(), args.into(), self.state)?;
|
||||
let return_val = FuncInstance::invoke(nested_func.clone(), args.into(), self.externals)?;
|
||||
if let Some(return_val) = return_val {
|
||||
function_context.value_stack_mut().push(return_val)?;
|
||||
}
|
||||
@ -439,13 +439,14 @@ impl<'a, 'b: 'a> Interpreter<'a, 'b> {
|
||||
.expect("Due to validation table should exists");
|
||||
let func_ref = table.get(table_func_idx)?;
|
||||
|
||||
{
|
||||
let actual_function_type = func_ref.func_type();
|
||||
let required_function_type = context
|
||||
.module()
|
||||
.type_by_index(type_idx)
|
||||
.expect("Due to validation type should exists");
|
||||
|
||||
if required_function_type != actual_function_type {
|
||||
if &*required_function_type != actual_function_type {
|
||||
return Err(Error::Function(format!(
|
||||
"expected function with signature ({:?}) -> {:?} when got with ({:?}) -> {:?}",
|
||||
required_function_type.params(),
|
||||
@ -454,6 +455,7 @@ impl<'a, 'b: 'a> Interpreter<'a, 'b> {
|
||||
actual_function_type.return_type()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InstructionOutcome::ExecuteCall(func_ref))
|
||||
}
|
||||
|
@ -1,138 +0,0 @@
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub unsafe trait StateKey {
|
||||
type Static: ?Sized + 'static;
|
||||
}
|
||||
|
||||
pub fn type_id<T>() -> TypeId
|
||||
where
|
||||
T: StateKey,
|
||||
T::Static: Any,
|
||||
{
|
||||
TypeId::of::<T::Static>()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HostState<'a> {
|
||||
data: HashMap<TypeId, *mut ()>,
|
||||
_marker: ::std::marker::PhantomData<&'a mut ()>,
|
||||
}
|
||||
|
||||
impl<'a> HostState<'a> {
|
||||
pub fn new() -> Self {
|
||||
HostState {
|
||||
data: HashMap::default(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert<V: StateKey + 'a>(&mut self, val: &'a mut V) {
|
||||
let ty_id = type_id::<V>();
|
||||
let ptr = val as *mut V as *mut ();
|
||||
let existing = self.data.insert(ty_id, ptr);
|
||||
assert!(existing.is_none());
|
||||
}
|
||||
|
||||
pub fn with<V: StateKey + 'a, R, F: FnOnce(&V) -> R>(&self, f: F) -> R {
|
||||
let ptr = self.data.get(&type_id::<V>()).unwrap();
|
||||
unsafe {
|
||||
let val_ref = &*(*ptr as *const V);
|
||||
f(val_ref)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_mut<V: StateKey + 'a, R, F: FnOnce(&mut V) -> R + 'static>(&mut self, f: F) -> R {
|
||||
let ptr = self.data.get_mut(&type_id::<V>()).unwrap();
|
||||
unsafe {
|
||||
let val_ref = &mut *(*ptr as *mut V);
|
||||
f(val_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct MyState<'a>(&'a mut i32);
|
||||
impl<'a> MyState<'a> {
|
||||
fn inc(&mut self) {
|
||||
*self.0 += 1;
|
||||
}
|
||||
|
||||
fn get(&self) -> i32 {
|
||||
*self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety of this impl should be ensured by a macro.
|
||||
unsafe impl<'a> StateKey for MyState<'a> {
|
||||
type Static = MyState<'static>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut counter = 33i32;
|
||||
|
||||
let new_value = {
|
||||
let mut my_state = MyState(&mut counter);
|
||||
let mut host_state = HostState::new();
|
||||
host_state.insert::<MyState>(&mut my_state);
|
||||
host_state.with_mut(|my_state: &mut MyState| {
|
||||
my_state.inc();
|
||||
my_state.get()
|
||||
})
|
||||
};
|
||||
|
||||
assert_eq!(new_value, counter);
|
||||
}
|
||||
|
||||
struct MyImmutableState<'a>(&'a i32);
|
||||
/// Safety of this impl should be ensured by a macro.
|
||||
unsafe impl<'a> StateKey for MyImmutableState<'a> {
|
||||
type Static = MyImmutableState<'static>;
|
||||
}
|
||||
|
||||
struct StaticState(i32);
|
||||
|
||||
impl StaticState {
|
||||
fn inc(&mut self) {
|
||||
self.0 += 1;
|
||||
}
|
||||
|
||||
fn get(&self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety of this impl should be ensured by a macro.
|
||||
unsafe impl<'a> StateKey for StaticState {
|
||||
type Static = StaticState;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compiles_with_static() {
|
||||
let mut static_state = StaticState(45);
|
||||
let mut host_state = HostState::new();
|
||||
host_state.insert::<StaticState>(&mut static_state);
|
||||
host_state.with_mut(|my_state: &mut StaticState| {
|
||||
my_state.inc();
|
||||
});
|
||||
host_state.with_mut(|my_state: &mut StaticState| {
|
||||
my_state.inc();
|
||||
assert_eq!(47, my_state.get());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn doesnt_allow_dups() {
|
||||
let mut static_state_1 = StaticState(45);
|
||||
let mut static_state_2 = StaticState(45);
|
||||
let mut host_state = HostState::new();
|
||||
host_state.insert::<StaticState>(&mut static_state_1);
|
||||
host_state.insert::<StaticState>(&mut static_state_2);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user