diff --git a/Cargo.toml b/Cargo.toml index d78cb4b9f415..787fe7e487a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ cranelift-entity = { version = "0.49", features = ["enable-serde"] } cranelift-wasm = { version = "0.49", features = ["enable-serde"] } cranelift-native = "0.49" wasmtime = { path = "crates/api" } +wasmtime-bindings = { path = "crates/bindings" } +wasmtime-bindings-macro = { path = "crates/bindings/macro" } wasmtime-debug = { path = "crates/debug" } wasmtime-environ = { path = "crates/environ" } wasmtime-interface-types = { path = "crates/interface-types" } diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index f649eccbaeab..51aba285081e 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -52,6 +52,8 @@ serde = { "version" = "1.0.94", features = ["derive"] } pretty_env_logger = "0.3.0" wasmtime-wast = { path = "../wast" } wasmtime-wasi = { path = "../wasi" } +wasmtime-bindings = { path = "../bindings" } +wasmtime-bindings-macro = { path = "../bindings/macro" } rayon = "1.1" file-per-thread-logger = "0.1.1" diff --git a/crates/api/examples/hello2.rs b/crates/api/examples/hello2.rs new file mode 100644 index 000000000000..308f1bb165fa --- /dev/null +++ b/crates/api/examples/hello2.rs @@ -0,0 +1,74 @@ +//! Translation of hello example + +use anyhow::{bail, format_err, Result}; +use std::cell::Ref; +use std::fs::read; +use wasmtime_api::*; + +#[macro_use] +extern crate wasmtime_bindings_macro; + +use wasmtime_bindings::*; + +#[wasmtime_method(module(callback_mod))] +fn callback() { + println!("Calling back..."); + println!("> Hello World!"); +} + +#[wasmtime_method(module(hello_mod))] +fn hello() { + unimplemented!(); +} + +fn main() -> Result<()> { + // Initialize. + println!("Initializing..."); + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); + + // Load binary. + println!("Loading binary..."); + let binary = read("examples/hello.wasm")?; + + // Compile. + println!("Compiling module..."); + let module = HostRef::new( + Module::new(&store, &binary).map_err(|_| format_err!("> Error compiling module!"))?, + ); + + // Create external print functions. + println!("Creating callback..."); + let hello_func = HostRef::new(wrap_wasmtime_func!(&store; module(callback_mod))); + + // Instantiate. + println!("Instantiating module..."); + let imports = vec![hello_func.into()]; + let instance = HostRef::new( + Instance::new(&store, &module, imports.as_slice()) + .map_err(|_| format_err!("> Error instantiating module!"))?, + ); + + // Extract export. + println!("Extracting export..."); + let exports = Ref::map(instance.borrow(), |instance| instance.exports()); + if exports.len() == 0 { + bail!("> Error accessing exports!"); + } + let run_func = exports[0] + .func() + .ok_or_else(|| format_err!("> Error accessing exports!"))?; + + // Call. + println!("Calling export..."); + let f = get_wasmtime_func!(run_func; module(hello_mod)); + f.call(); + + // Shut down. + println!("Shutting down..."); + drop(store); + + // All done. + println!("Done."); + Ok(()) +} diff --git a/crates/api/examples/hello3.rs b/crates/api/examples/hello3.rs new file mode 100644 index 000000000000..8c4530dda6c1 --- /dev/null +++ b/crates/api/examples/hello3.rs @@ -0,0 +1,80 @@ +//! Translation of hello example + +use anyhow::{format_err, Result}; +use std::fs::read; +use wasmtime_api::*; + +#[macro_use] +extern crate wasmtime_bindings_macro; + +#[wasmtime_trait(module(callback_mod))] +trait Callback { + fn callback(&self); +} + +struct CallbackImpl; + +impl Callback for CallbackImpl { + fn callback(&self) { + println!("Calling back..."); + println!("> Hello World!"); + } +} + +#[wasmtime_trait(module(hello_mod))] +trait Hello { + fn run(&self); +} + +fn main() -> Result<()> { + // Initialize. + println!("Initializing..."); + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); + + // Load binary. + println!("Loading binary..."); + let binary = read("examples/hello.wasm")?; + + // Compile. + println!("Compiling module..."); + let module = HostRef::new( + Module::new(&store, &binary).map_err(|_| format_err!("> Error compiling module!"))?, + ); + + // Create external print functions. + println!("Creating callback..."); + let callback_mod = HostRef::new( + wrap_wasmtime_module!( + &store, |_imports| CallbackImpl; module(callback_mod) + ) + .map_err(|_| format_err!("> Error compiling callback module!"))?, + ); + let callback_instance = Instance::new(&store, &callback_mod, &[]) + .map_err(|_| format_err!("> Error instantiating callback module!"))?; + let hello_func = &callback_instance.exports()[0]; + + // Instantiate. + println!("Instantiating module..."); + let imports = vec![hello_func.clone()]; + let instance = HostRef::new( + Instance::new(&store, &module, imports.as_slice()) + .map_err(|_| format_err!("> Error instantiating module!"))?, + ); + + // Extract export. + println!("Extracting export..."); + let hello = map_to_wasmtime_trait!(instance; module(hello_mod)); + + // Call. + println!("Calling export..."); + hello.run(); + + // Shut down. + println!("Shutting down..."); + drop(store); + + // All done. + println!("Done."); + Ok(()) +} diff --git a/crates/api/examples/hello4.rs b/crates/api/examples/hello4.rs new file mode 100644 index 000000000000..fbe6a4453124 --- /dev/null +++ b/crates/api/examples/hello4.rs @@ -0,0 +1,76 @@ +//! Translation of hello example + +use anyhow::{format_err, Result}; +use std::fs::read; +use wasmtime_api::*; + +#[macro_use] +extern crate wasmtime_bindings_macro; + +struct Callback; + +#[wasmtime_impl(module(callback_mod))] +impl Callback { + fn callback(&self) { + println!("Calling back..."); + println!("> Hello World!"); + } +} + +#[wasmtime_trait(module(hello_mod))] +trait Hello { + fn run(&self); +} + +fn main() -> Result<()> { + // Initialize. + println!("Initializing..."); + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); + + // Load binary. + println!("Loading binary..."); + let binary = read("examples/hello.wasm")?; + + // Compile. + println!("Compiling module..."); + let module = HostRef::new( + Module::new(&store, &binary).map_err(|_| format_err!("> Error compiling module!"))?, + ); + + // Create external print functions. + println!("Creating callback..."); + let callback_mod = HostRef::new( + wrap_wasmtime_module!( + &store, |_imports| Callback; module(callback_mod) + ) + .map_err(|_| format_err!("> Error compiling callback module!"))?, + ); + let callback_instance = Instance::new(&store, &callback_mod, &[]) + .map_err(|_| format_err!("> Error instantiating callback module!"))?; + let hello_func = &callback_instance.exports()[0]; + + // Instantiate. + println!("Instantiating module..."); + let imports = vec![hello_func.clone()]; + let instance = HostRef::new( + Instance::new(&store, &module, imports.as_slice()) + .map_err(|_| format_err!("> Error instantiating module!"))?, + ); + + // Extract export. + println!("Extracting export..."); + let hello = map_to_wasmtime_trait!(&instance; module(hello_mod)); + + // Call. + println!("Calling export..."); + hello.run(); + + // Shut down. + println!("Shutting down..."); + drop(store); + + // All done. + println!("Done."); + Ok(()) +} diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 2a86789377c1..649a22c94f94 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -1,6 +1,6 @@ use crate::r#ref::HostRef; use crate::runtime::Store; -use crate::trampoline::generate_func_export; +use crate::trampoline::{generate_func_export, wrap_func_export}; use crate::trap::Trap; use crate::types::FuncType; use crate::values::Val; @@ -151,3 +151,33 @@ impl WrappedCallable for NativeCallable { &self.export } } + +pub struct RawCallable { + instance: InstanceHandle, + export: Export, +} + +impl RawCallable { + pub fn new(address: *const u8, signature: ir::Signature, store: &HostRef) -> Self { + let (instance, export) = wrap_func_export( + address as *const wasmtime_runtime::VMFunctionBody, + signature, + store, + ) + .expect("wrapped export"); + RawCallable { instance, export } + } +} + +impl WrappedCallable for RawCallable { + fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), HostRef> { + // TODO similar to WasmtimeFn::call + unimplemented!(); + } + fn wasmtime_handle(&self) -> &InstanceHandle { + &self.instance + } + fn wasmtime_export(&self) -> &Export { + &self.export + } +} diff --git a/crates/api/src/externals.rs b/crates/api/src/externals.rs index 56aca24ad50a..dcf82d5c6820 100644 --- a/crates/api/src/externals.rs +++ b/crates/api/src/externals.rs @@ -1,4 +1,4 @@ -use crate::callable::{Callable, NativeCallable, WasmtimeFn, WrappedCallable}; +use crate::callable::{Callable, NativeCallable, RawCallable, WasmtimeFn, WrappedCallable}; use crate::r#ref::{AnyRef, HostRef}; use crate::runtime::Store; use crate::trampoline::{generate_global_export, generate_memory_export, generate_table_export}; @@ -135,6 +135,23 @@ impl Func { } } + pub fn from_raw( + store: &HostRef, + address: *const u8, + signature: cranelift_codegen::ir::Signature, + ) -> Self { + let callable = RawCallable::new(address, signature.clone(), store); + let ty = FuncType::from_cranelift_signature(signature); + Func::from_wrapped(store, ty, Rc::new(callable)) + } + + pub fn raw_parts(&self) -> (wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export) { + ( + self.callable.wasmtime_handle().clone(), + self.callable.wasmtime_export().clone(), + ) + } + pub fn r#type(&self) -> &FuncType { &self.r#type } diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 24be6a614b1d..f45f3e2ebf51 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -1,8 +1,9 @@ use crate::context::Context; use crate::externals::Extern; -use crate::module::Module; +use crate::module::{Module, ModuleCodeSource}; use crate::r#ref::HostRef; use crate::runtime::Store; +use crate::trampoline::create_handle_for_trait; use crate::{HashMap, HashSet}; use alloc::string::{String, ToString}; use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, vec::Vec}; @@ -70,8 +71,26 @@ impl Instance { .zip(externs.iter()) .map(|(i, e)| (i.module().to_string(), i.name().to_string(), e.clone())) .collect::>(); - let (mut instance_handle, contexts) = - instantiate_in_context(module.borrow().binary(), imports, context, exports)?; + + let (mut instance_handle, contexts) = match module.borrow().source() { + ModuleCodeSource::Binary(binary) => { + instantiate_in_context(binary, imports, context, exports)? + } + ModuleCodeSource::Factory { + state_builder, + addresses, + } => { + let exports = module + .borrow() + .exports() + .iter() + .map(|e| e.clone()) + .zip(addresses.clone().into_iter()) + .collect::>(); + let handle = create_handle_for_trait(&**state_builder, &exports, externs, &store)?; + (handle, HashSet::new()) + } + }; let exports = { let module = module.borrow(); diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 065b8c7bae09..21d63d28e53e 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -6,6 +6,7 @@ mod callable; mod context; mod externals; mod instance; +mod r#macro; mod module; mod r#ref; mod runtime; @@ -21,7 +22,7 @@ extern crate alloc; pub use crate::callable::Callable; pub use crate::externals::*; pub use crate::instance::Instance; -pub use crate::module::Module; +pub use crate::module::{HandleStateBuilder, Module}; pub use crate::r#ref::{AnyRef, HostInfo, HostRef}; pub use crate::runtime::{Config, Engine, Store}; pub use crate::trap::Trap; diff --git a/crates/api/src/macro.rs b/crates/api/src/macro.rs new file mode 100644 index 000000000000..0b35e0e82250 --- /dev/null +++ b/crates/api/src/macro.rs @@ -0,0 +1,48 @@ +#[macro_export] +macro_rules! wrap_wasmtime_func { + ($store:expr; module( $module_name:path )) => {{ + use $module_name as m; + let f = m::metadata(); + ::wasmtime_api::Func::from_raw($store, f.address, f.signature) + }}; +} + +#[macro_export] +macro_rules! get_wasmtime_func { + ($func:expr; module( $module_name:path )) => {{ + use $module_name as m; + let (i, e) = $func.borrow().raw_parts(); + m::Wrapper::new(i, e) + }}; +} + +#[macro_export] +macro_rules! map_to_wasmtime_trait { + ($instance:expr; module( $module_name:path )) => {{ + use $module_name as m; + let handle = $instance.borrow().handle().clone(); + m::Wrapper::new(handle) + }}; +} + +#[macro_export] +macro_rules! wrap_wasmtime_module { + ($store:expr, |$imports:ident| $factory:expr; module( $module_name:path )) => {{ + use $module_name as m; + struct T; + impl ::wasmtime_api::HandleStateBuilder for T { + fn build_state(&self, imports: &[::wasmtime_api::Extern]) -> Box { + let imp = |$imports: &[::wasmtime_api::Extern]| $factory; + let state = m::State { + subject: ::std::cell::RefCell::new(::std::boxed::Box::new(imp(imports))), + }; + ::std::boxed::Box::new(state) + } + } + let exports = m::metadata() + .into_iter() + .map(|f| (String::from(f.name), f.signature, f.address)) + .collect::>(); + ::wasmtime_api::Module::from_raw_parts($store, &exports, ::std::rc::Rc::new(T)) + }}; +} diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index 5499fed94ded..0705acbe3308 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -1,3 +1,4 @@ +use crate::externals::Extern; use crate::r#ref::HostRef; use crate::runtime::Store; use crate::types::{ @@ -6,6 +7,9 @@ use crate::types::{ }; use alloc::{boxed::Box, string::String, vec::Vec}; use anyhow::Result; +use std::any::Any; +use std::rc::Rc; + use wasmparser::{validate, ExternalKind, ImportSectionEntryType, ModuleReader, SectionCode}; fn into_memory_type(mt: wasmparser::MemoryType) -> MemoryType { @@ -170,26 +174,39 @@ fn read_imports_and_exports(binary: &[u8]) -> Result<(Box<[ImportType]>, Box<[Ex Ok((imports.into_boxed_slice(), exports.into_boxed_slice())) } +pub trait HandleStateBuilder { + fn build_state(&self, imports: &[Extern]) -> Box; +} + #[derive(Clone)] pub struct Module { store: HostRef, - binary: Box<[u8]>, + source: ModuleCodeSource, imports: Box<[ImportType]>, exports: Box<[ExportType]>, } +#[derive(Clone)] +pub(crate) enum ModuleCodeSource { + Binary(Box<[u8]>), + Factory { + state_builder: Rc, + addresses: Vec<*const wasmtime_runtime::VMFunctionBody>, + }, +} + impl Module { pub fn new(store: &HostRef, binary: &[u8]) -> Result { let (imports, exports) = read_imports_and_exports(binary)?; Ok(Module { store: store.clone(), - binary: binary.into(), + source: ModuleCodeSource::Binary(binary.into()), imports, exports, }) } - pub(crate) fn binary(&self) -> &[u8] { - &self.binary + pub(crate) fn source(&self) -> &ModuleCodeSource { + &self.source } pub fn validate(_store: &Store, binary: &[u8]) -> bool { validate(binary, None).is_ok() @@ -200,4 +217,29 @@ impl Module { pub fn exports(&self) -> &[ExportType] { &self.exports } + pub fn from_raw_parts( + store: &HostRef, + exports: &[(String, cranelift_codegen::ir::Signature, *const u8)], + state_builder: Rc, + ) -> Result { + let (exports, addresses): (Vec<_>, Vec<_>) = exports + .iter() + .map(|(name, sig, address)| { + let ft = FuncType::from_cranelift_signature(sig.clone()); + ( + ExportType::new(name.clone().into(), ExternType::ExternFunc(ft)), + *address as *const wasmtime_runtime::VMFunctionBody, + ) + }) + .unzip(); + Ok(Module { + store: store.clone(), + source: ModuleCodeSource::Factory { + state_builder, + addresses, + }, + imports: Box::new([]), + exports: exports.into_boxed_slice(), + }) + } } diff --git a/crates/api/src/trampoline/create_handle.rs b/crates/api/src/trampoline/create_handle.rs index a4a6f780eeca..e2fa936d4618 100644 --- a/crates/api/src/trampoline/create_handle.rs +++ b/crates/api/src/trampoline/create_handle.rs @@ -19,9 +19,10 @@ pub(crate) fn create_handle( signature_registry: Option>, finished_functions: PrimaryMap, state: Box, + global_exports: Option>>>>, ) -> Result { let global_exports: Rc>>> = - Rc::new(RefCell::new(HashMap::new())); + global_exports.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))); let imports = Imports::new( HashSet::new(), diff --git a/crates/api/src/trampoline/func.rs b/crates/api/src/trampoline/func.rs index a2272a16d23e..c5fe2874c8cc 100644 --- a/crates/api/src/trampoline/func.rs +++ b/crates/api/src/trampoline/func.rs @@ -2,7 +2,7 @@ use super::create_handle::create_handle; use crate::r#ref::HostRef; -use crate::{Callable, FuncType, Store, Trap, Val}; +use crate::{Callable, ExportType, Extern, ExternType, FuncType, Store, Trap, Val}; use alloc::{boxed::Box, rc::Rc, string::ToString, vec::Vec}; use anyhow::Result; use core::cmp; @@ -239,6 +239,72 @@ pub fn create_handle_with_function( Some(store.borrow_mut()), finished_functions, Box::new(trampoline_state), + None, + ) +} + +pub fn create_handle_for_wrapped( + address: *const VMFunctionBody, + sig: ir::Signature, + store: &HostRef, +) -> Result { + let mut module = Module::new(); + let mut finished_functions: PrimaryMap = + PrimaryMap::new(); + + let sig_id = module.signatures.push(sig.clone()); + let func_id = module.functions.push(sig_id); + module + .exports + .insert("wrapped".to_string(), Export::Function(func_id)); + + finished_functions.push(address); + + create_handle( + module, + Some(store.borrow_mut()), + finished_functions, + Box::new(()), + None, + ) +} + +pub fn create_handle_for_trait( + state_builder: &dyn crate::module::HandleStateBuilder, + exports: &[(ExportType, *const wasmtime_runtime::VMFunctionBody)], + externs: &[Extern], + store: &HostRef, +) -> Result { + let mut module = Module::new(); + let mut finished_functions: PrimaryMap< + DefinedFuncIndex, + *const wasmtime_runtime::VMFunctionBody, + > = PrimaryMap::new(); + + for (e, address) in exports { + let f = if let ExternType::ExternFunc(f) = e.r#type() { + f + } else { + panic!(); + }; + let sig_id = module.signatures.push(f.get_cranelift_signature().clone()); + let func_id = module.functions.push(sig_id); + module + .exports + .insert(e.name().to_string(), Export::Function(func_id)); + + finished_functions.push(*address); + } + + let state = state_builder.build_state(externs); + + let exports = store.borrow().global_exports().clone(); + create_handle( + module, + Some(store.borrow_mut()), + finished_functions, + state, + Some(exports), ) } diff --git a/crates/api/src/trampoline/global.rs b/crates/api/src/trampoline/global.rs index 7e96618a767f..e5ecec5d8ba7 100644 --- a/crates/api/src/trampoline/global.rs +++ b/crates/api/src/trampoline/global.rs @@ -33,7 +33,7 @@ pub fn create_global(gt: &GlobalType, val: Val) -> Result<(wasmtime_runtime::Exp initializer: cranelift_wasm::GlobalInit::Import, // TODO is it right? }; let mut handle = - create_handle(Module::new(), None, PrimaryMap::new(), Box::new(())).expect("handle"); + create_handle(Module::new(), None, PrimaryMap::new(), Box::new(()), None).expect("handle"); Ok(( wasmtime_runtime::Export::Global { definition: definition.as_mut(), diff --git a/crates/api/src/trampoline/memory.rs b/crates/api/src/trampoline/memory.rs index 898fb71d8e64..73805c52a6dd 100644 --- a/crates/api/src/trampoline/memory.rs +++ b/crates/api/src/trampoline/memory.rs @@ -30,5 +30,5 @@ pub fn create_handle_with_memory(memory: &MemoryType) -> Result wasmtime_environ::Export::Memory(memory_id), ); - create_handle(module, None, PrimaryMap::new(), Box::new(())) + create_handle(module, None, PrimaryMap::new(), Box::new(()), None) } diff --git a/crates/api/src/trampoline/mod.rs b/crates/api/src/trampoline/mod.rs index 6b5dbf1220a8..692e2d8f6ad1 100644 --- a/crates/api/src/trampoline/mod.rs +++ b/crates/api/src/trampoline/mod.rs @@ -6,7 +6,7 @@ mod global; mod memory; mod table; -use self::func::create_handle_with_function; +use self::func::{create_handle_for_wrapped, create_handle_with_function}; use self::global::create_global; use self::memory::create_handle_with_memory; use self::table::create_handle_with_table; @@ -27,6 +27,18 @@ pub fn generate_func_export( Ok((instance, export)) } +pub fn wrap_func_export( + address: *const wasmtime_runtime::VMFunctionBody, + sig: cranelift_codegen::ir::Signature, + store: &HostRef, +) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> { + let mut instance = create_handle_for_wrapped(address, sig, store)?; + let export = instance.lookup("wrapped").expect("wrapped export"); + Ok((instance, export)) +} + +pub use func::create_handle_for_trait; + pub fn generate_global_export( gt: &GlobalType, val: Val, diff --git a/crates/api/src/trampoline/table.rs b/crates/api/src/trampoline/table.rs index 692d43164d2c..70563084d1da 100644 --- a/crates/api/src/trampoline/table.rs +++ b/crates/api/src/trampoline/table.rs @@ -32,5 +32,5 @@ pub fn create_handle_with_table(table: &TableType) -> Result { wasmtime_environ::Export::Table(table_id), ); - create_handle(module, None, PrimaryMap::new(), Box::new(())) + create_handle(module, None, PrimaryMap::new(), Box::new(()), None) } diff --git a/crates/bindings/.gitignore b/crates/bindings/.gitignore new file mode 100644 index 000000000000..693699042b1a --- /dev/null +++ b/crates/bindings/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/crates/bindings/Cargo.toml b/crates/bindings/Cargo.toml new file mode 100644 index 000000000000..2ee4292f2d9b --- /dev/null +++ b/crates/bindings/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wasmtime-bindings" +version = "0.2.0" +authors = ["The Wasmtime Project Developers"] +description = "Debug utils for WebAsssembly code in Cranelift" +repository = "https://github.com/CraneStation/wasmtime" +documentation = "https://docs.rs/wasmtime-bindings-macro/" +categories = ["wasm"] +keywords = ["webassembly", "wasm", "debuginfo"] +license = "Apache-2.0 WITH LLVM-exception" +readme = "README.md" +edition = "2018" + +[dependencies] +cranelift-codegen = "0.49.0" +wasmtime-runtime = { path = "../runtime" } +target-lexicon = { version = "0.9.0", default-features = false } diff --git a/crates/bindings/macro/.gitignore b/crates/bindings/macro/.gitignore new file mode 100644 index 000000000000..693699042b1a --- /dev/null +++ b/crates/bindings/macro/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/crates/bindings/macro/Cargo.toml b/crates/bindings/macro/Cargo.toml new file mode 100644 index 000000000000..86eeb60dc769 --- /dev/null +++ b/crates/bindings/macro/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "wasmtime-bindings-macro" +version = "0.2.0" +authors = ["The Wasmtime Project Developers"] +description = "Debug utils for WebAsssembly code in Cranelift" +repository = "https://github.com/CraneStation/wasmtime" +documentation = "https://docs.rs/wasmtime-bindings-macro/" +categories = ["wasm"] +keywords = ["webassembly", "wasm", "debuginfo"] +license = "Apache-2.0 WITH LLVM-exception" +readme = "README.md" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.2" +syn = "1.0.5" +proc-macro2 = "1.0.3" + +[features] +default = ["syn/full", "syn/extra-traits"] diff --git a/crates/bindings/macro/src/attr.rs b/crates/bindings/macro/src/attr.rs new file mode 100644 index 000000000000..9560783d669e --- /dev/null +++ b/crates/bindings/macro/src/attr.rs @@ -0,0 +1,50 @@ +use syn::parse::{Parse, ParseStream}; +use syn::Visibility; +use syn::{Ident, Path, Result}; + +pub(crate) struct TransformAttributes { + pub module: Option, + pub visibility: Option, + pub context: Option, +} + +impl Parse for TransformAttributes { + fn parse(input: ParseStream) -> Result { + let mut module = None; + let mut context = None; + let mut visibility = None; + + while !input.is_empty() { + let i: Ident = input.parse()?; + match i.to_string().as_str() { + "module" => { + let content; + parenthesized!(content in input); + module = Some(content.parse::()?); + } + "context" => { + let content; + parenthesized!(content in input); + context = Some(content.parse::()?); + } + "visibility" => { + let content; + parenthesized!(content in input); + visibility = Some(content.parse::()?); + } + _ => { + return Err(input.error(format!("unexpected attr name {}", i.to_string()))); + } + } + if input.is_empty() { + break; + } + input.parse::()?; + } + Ok(TransformAttributes { + module, + context, + visibility, + }) + } +} diff --git a/crates/bindings/macro/src/impl.rs b/crates/bindings/macro/src/impl.rs new file mode 100644 index 000000000000..9a6cd356931f --- /dev/null +++ b/crates/bindings/macro/src/impl.rs @@ -0,0 +1,205 @@ +use crate::attr::TransformAttributes; +use crate::method::{need_context, transform_sig, TransformSignatureResult}; +use crate::signature::{read_signature, Parameter, ParameterType}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Ident, ImplItem, ImplItemMethod, ItemImpl, Type, Visibility}; + +fn generate_method_wrapper( + m: &ImplItemMethod, + wasm_bindings_common: TokenStream, + attr: &TransformAttributes, +) -> (TokenStream, TokenStream, TokenStream, TokenStream, Ident) { + let rsig = read_signature(&m.sig, &attr.context); + let _self_ref = match rsig.params.get(0) { + Some(Parameter { + ty: ParameterType::SelfRef(ref sr), + .. + }) => sr, + _ => { + panic!("expected first parameter to be self ref"); + } + }; + + let (build_context, build_cb_context, context_name) = if need_context(&rsig) { + if let Some(context_name) = &attr.context { + ( + quote! { + let _ctx = #context_name :: from_vmctx(vmctx); + }, + quote! { + let _ctx = #context_name :: from_vmctx(self.vmctx); + }, + quote! { #context_name }, + ) + } else { + ( + quote! { + let _ctx = #wasm_bindings_common :: VMContextWrapper(vmctx); + }, + quote! { + let _ctx = #wasm_bindings_common :: VMContextWrapper(self.vmctx); + }, + quote! { #wasm_bindings_common :: VMContextWrapper }, + ) + } + } else { + (quote! {}, quote! {}, quote! { panic!() }) + }; + + let TransformSignatureResult { + abi_params, + abi_return, + params_conversion, + ret_conversion, + call_args, + sig_build, + cb_abi_params, + cb_abi_return, + cb_params_conversion, + cb_ret_conversion, + cb_call_args, + } = transform_sig(&rsig, context_name, wasm_bindings_common.clone()); + let name = &m.sig.ident; + let result = quote! { + pub extern fn #name (#abi_params) #abi_return { + #build_context + #params_conversion + let _res = _self . #name ( #call_args ); + #ret_conversion + } + }; + let call_wrapper = quote! { + pub fn #name(&self, #cb_abi_params) #cb_abi_return { + type F = extern fn(#abi_params) #abi_return; + let (_f, vmctx) = #wasm_bindings_common :: get_body(&self . #name); + let _f: F = unsafe { std::mem::transmute(_f) }; + #build_cb_context + #cb_params_conversion + let _res = _f(#cb_call_args); + #cb_ret_conversion + } + }; + let sig_build = quote! { + pub fn #name () -> ir::Signature { + #sig_build + sig + } + }; + let fn_type = quote! { + extern fn(#abi_params) #abi_return + }; + (result, sig_build, call_wrapper, fn_type, name.clone()) +} + +pub(crate) fn wrap_impl(tr: ItemImpl, attr: TransformAttributes) -> TokenStream { + let vis = attr.visibility.as_ref().unwrap_or(&Visibility::Inherited); + + let ident = if let Type::Path(tp) = tr.self_ty.as_ref() { + tp + } else { + panic!("only path type is allowed") + }; + + let mod_name = attr.module.as_ref().unwrap(); + let wasmtime_bindings = quote! { :: wasmtime_bindings }; + let mut mod_wrappers = TokenStream::new(); + let mut signatures = TokenStream::new(); + let mut wrapper_fields = TokenStream::new(); + let mut wrapper_fields_init = TokenStream::new(); + let mut wrapper_impl = TokenStream::new(); + let mut metadata = TokenStream::new(); + for i in &tr.items { + if let ImplItem::Method(ref m) = i { + let (wrapper, signature, call_wrapper, fn_type, export) = + generate_method_wrapper(m, wasmtime_bindings.clone(), &attr); + mod_wrappers.extend(wrapper); + signatures.extend(signature); + + let export_name = export.to_string(); + wrapper_fields.extend(quote! { + #export: InstanceHandleExport, + }); + wrapper_fields_init.extend(quote! { + #export: instance.lookup(#export_name).unwrap(), + }); + wrapper_impl.extend(call_wrapper); + metadata.extend(quote! { + #wasmtime_bindings :: FnMetadata { + name: #export_name, + signature: signatures::#export(), + address: #export as #fn_type as *const _, + }, + }); + } + } + + let mod_item_vis = match vis { + Visibility::Public(p) => quote! { #p }, + Visibility::Crate(c) => quote! { #c }, + Visibility::Restricted(r) => quote! { #r }, + Visibility::Inherited => quote! { pub(super) }, + }; + let mod_content = quote! { + #mod_item_vis mod #mod_name { + use super::*; + use #wasmtime_bindings :: { + VMContext, InstanceHandle, InstanceHandleExport, + AbiPrimitive, WasmMem + }; + use ::std::boxed::Box; + use ::std::cell::{Ref, RefMut, RefCell}; + type Subject = super :: #ident; + + pub struct State { + #mod_item_vis subject: RefCell< + Box < super :: #ident > + >, + } + impl State { + fn from<'a>(vmctx: *mut VMContext) -> &'a mut Self { + unsafe { &mut *(&mut *vmctx).host_state().downcast_mut::().unwrap() } + } + } + #vis fn get_self(vmctx: *mut VMContext) -> Ref<'static, Subject> { + use ::core::ops::Deref; + Ref::map(State::from(vmctx).subject.borrow(), |b| b.deref()) + } + #vis fn get_self_mut(vmctx: *mut VMContext) -> RefMut<'static, Subject> { + use ::core::ops::DerefMut; + RefMut::map(State::from(vmctx).subject.borrow_mut(), |b| b.deref_mut()) + } + #mod_wrappers + + pub struct Wrapper { + #wrapper_fields + vmctx: *mut VMContext, + } + impl Wrapper { + pub fn new(mut instance: InstanceHandle) -> Self { + Wrapper { + #wrapper_fields_init + vmctx: instance.vmctx_mut_ptr(), + } + } + + #wrapper_impl + } + + pub mod signatures { + use super::*; + use #wasmtime_bindings :: codegen :: {ir, isa}; + #signatures + } + + pub fn metadata() -> Vec<#wasmtime_bindings :: FnMetadata> { + vec![#metadata] + } + } + }; + quote! { + #tr + + #mod_content + } +} diff --git a/crates/bindings/macro/src/lib.rs b/crates/bindings/macro/src/lib.rs new file mode 100644 index 000000000000..468f4b498d55 --- /dev/null +++ b/crates/bindings/macro/src/lib.rs @@ -0,0 +1,34 @@ +extern crate proc_macro; +extern crate proc_macro2; +#[macro_use] +extern crate syn; +extern crate quote; + +mod attr; +mod r#impl; +mod method; +mod signature; +mod r#trait; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn wasmtime_method(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr as attr::TransformAttributes); + let input = syn::parse_macro_input!(item as syn::ItemFn); + method::wrap_method(input, attr).into() +} + +#[proc_macro_attribute] +pub fn wasmtime_trait(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr as attr::TransformAttributes); + let input = syn::parse_macro_input!(item as syn::ItemTrait); + r#trait::wrap_trait(input, attr).into() +} + +#[proc_macro_attribute] +pub fn wasmtime_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr as attr::TransformAttributes); + let input = syn::parse_macro_input!(item as syn::ItemImpl); + r#impl::wrap_impl(input, attr).into() +} diff --git a/crates/bindings/macro/src/method.rs b/crates/bindings/macro/src/method.rs new file mode 100644 index 000000000000..6acfd6490b5d --- /dev/null +++ b/crates/bindings/macro/src/method.rs @@ -0,0 +1,309 @@ +use crate::attr::TransformAttributes; +use crate::signature::{read_signature, MethodSignature, ParameterType, PtrOrRef, Return}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{self, Ident, ItemFn}; + +pub(crate) fn need_context(sig: &MethodSignature) -> bool { + for p in &sig.params { + if let ParameterType::Ptr(_, _, _) = p.ty { + return true; + } + if let ParameterType::Context(_) = p.ty { + return true; + } + } + if let Some(Return::Ptr(_, _, _)) = sig.result { + return true; + } + false +} + +pub(crate) struct TransformSignatureResult { + pub abi_params: TokenStream, + pub abi_return: TokenStream, + pub params_conversion: TokenStream, + pub ret_conversion: TokenStream, + pub call_args: TokenStream, + pub sig_build: TokenStream, + pub cb_abi_params: TokenStream, + pub cb_abi_return: TokenStream, + pub cb_params_conversion: TokenStream, + pub cb_ret_conversion: TokenStream, + pub cb_call_args: TokenStream, +} +pub(crate) fn transform_sig( + sig: &MethodSignature, + context_name: TokenStream, + wasmtime_bindings: TokenStream, +) -> TransformSignatureResult { + let mut abi_params = TokenStream::new(); + let mut params_conversion = TokenStream::new(); + let mut call_args = TokenStream::new(); + let mut sig_build = TokenStream::new(); + + let mut cb_abi_params = TokenStream::new(); + let mut cb_params_conversion = TokenStream::new(); + let mut cb_call_args = TokenStream::new(); + + abi_params.extend(quote! { + vmctx: *mut VMContext, + }); + cb_call_args.extend(quote! { + vmctx, + }); + sig_build.extend(quote! { + let call_conv = #wasmtime_bindings :: get_host_call_conv(); + let mut sig = ir::Signature::new(call_conv); + sig.params.push(ir::AbiParam::special( + ir::types::I64, + ir::ArgumentPurpose::VMContext, + )); + }); + for (i, p) in sig.params.iter().enumerate() { + let id = p.id; + let internal_id = Ident::new(&format!("_a{}", i), Span::call_site()); + match p.ty { + ParameterType::VMContextMutPtr => { + call_args.extend(quote! { vmctx, }); + } + ParameterType::Context(_ref) => { + // TODO ref + call_args.extend(quote! { _ctx, }); + } + ParameterType::Ptr(_ty, _rp, _mut) => { + abi_params.extend(quote! { + #internal_id: <#context_name as #wasmtime_bindings :: WasmMem>::Abi, + }); + params_conversion.extend(quote! { + let #id = _ctx.as_ptr(#internal_id); + }); + if let PtrOrRef::Ptr = _rp { + call_args.extend(quote! { #id , }); + } else if _mut.is_some() { + call_args.extend(quote! { unsafe { &mut *#id } , }); + } else { + call_args.extend(quote! { unsafe { &*#id } , }); + } + sig_build.extend(quote! { + sig.params.push(ir::AbiParam::new( + #wasmtime_bindings :: get_ir_type::<<#context_name as #wasmtime_bindings :: WasmMem>::Abi>() + )); + }); + let param_ty = sig.original_params[i]; + cb_abi_params.extend(quote! { + #id: #param_ty, + }); + cb_params_conversion.extend(quote! { + let #internal_id = _ctx.as_off(#id); + }); + cb_call_args.extend(quote! { #internal_id , }); + } + ParameterType::Simple(ty) => { + abi_params.extend(quote! { + #internal_id: <#ty as #wasmtime_bindings :: AbiPrimitive>::Abi, + }); + params_conversion.extend(quote! { + let #id = <#ty as #wasmtime_bindings :: AbiPrimitive>::create_from_abi(#internal_id); + }); + call_args.extend(quote! { #id , }); + sig_build.extend(quote! { + sig.params.push(ir::AbiParam::new( + #wasmtime_bindings :: get_ir_type::<<#ty as #wasmtime_bindings :: AbiPrimitive>::Abi>() + )); + }); + let param_ty = sig.original_params[i]; + cb_abi_params.extend(quote! { + #id: #param_ty, + }); + cb_params_conversion.extend(quote! { + let #internal_id = #id.convert_to_abi(); + }); + cb_call_args.extend(quote! { #internal_id , }); + } + ParameterType::SelfRef(ref r) => { + params_conversion.extend(if r.is_some() { + quote! { let mut _self = get_self_mut(vmctx); } + } else { + quote! { let _self = get_self(vmctx); } + }); + } + } + } + let (abi_return, ret_conversion, sig_return, cb_ret_conversion) = match sig.result { + Some(Return::Ptr(_ty, _rp, _mut)) => ( + quote! { + -> <#context_name as #wasmtime_bindings :: WasmMem>::Abi + }, + quote! { + _ctx.as_off(_res) + }, + quote! { + sig.returns.push(ir::AbiParam::new( + #wasmtime_bindings :: get_ir_type::<<#context_name as #wasmtime_bindings :: WasmMem>::Abi>() + )); + }, + quote! { + _ctx.as_ptr(_res) + }, + ), + Some(Return::Simple(ty)) => ( + quote! { + -> <#ty as #wasmtime_bindings :: AbiPrimitive>::Abi + }, + quote! { + _res.convert_to_abi() + }, + quote! { + sig.returns.push(ir::AbiParam::new( + #wasmtime_bindings :: get_ir_type::<<#ty as #wasmtime_bindings :: AbiPrimitive>::Abi>() + )); + }, + quote! { + <#ty as #wasmtime_bindings :: AbiPrimitive>::create_from_abi(_res) + }, + ), + None => ( + TokenStream::new(), + TokenStream::new(), + TokenStream::new(), + TokenStream::new(), + ), + }; + sig_build.extend(sig_return); + let cb_abi_return = if let Some(ref r) = sig.original_result { + quote! { -> #r } + } else { + TokenStream::new() + }; + + TransformSignatureResult { + abi_params, + abi_return, + params_conversion, + ret_conversion, + call_args, + sig_build, + cb_abi_params, + cb_abi_return, + cb_params_conversion, + cb_ret_conversion, + cb_call_args, + } +} + +pub(crate) fn wrap_method(f: ItemFn, attr: TransformAttributes) -> TokenStream { + let sig = &f.sig; + let name = &sig.ident; + let vis = &f.vis; + assert!(sig.constness.is_none()); + assert!(sig.asyncness.is_none()); + assert!(sig.unsafety.is_none()); + assert!(sig.abi.is_none()); + //assert!(sig.generics) + let inputs = &sig.inputs; + assert!(sig.variadic.is_none()); + let output = &sig.output; + + let rsig = read_signature(sig, &attr.context); + + let body = &f.block; + + let wasmtime_bindings = quote! { :: wasmtime_bindings }; + let (build_context, build_cb_context, context_name) = if need_context(&rsig) { + if let Some(context_name) = attr.context { + ( + quote! { + let _ctx = #context_name :: from_vmctx(vmctx); + }, + quote! { + let _ctx = #context_name :: from_vmctx(self.vmctx); + }, + quote! { #context_name }, + ) + } else { + ( + quote! { + let _ctx = #wasmtime_bindings :: VMContextWrapper(vmctx); + }, + quote! { + let _ctx = #wasmtime_bindings :: VMContextWrapper(self.vmctx); + }, + quote! { #wasmtime_bindings :: VMContextWrapper }, + ) + } + } else { + (quote! {}, quote! {}, quote! { panic!("context n/a") }) + }; + + let TransformSignatureResult { + abi_params, + abi_return, + params_conversion, + ret_conversion, + call_args, + sig_build, + cb_abi_params, + cb_abi_return, + cb_params_conversion, + cb_ret_conversion, + cb_call_args, + } = transform_sig(&rsig, context_name, wasmtime_bindings.clone()); + + let def_module = if let Some(mod_name) = attr.module { + quote! { + #vis mod #mod_name { + use super::*; + use #wasmtime_bindings :: codegen :: {ir, isa}; + use #wasmtime_bindings :: { VMContext, InstanceHandle, InstanceHandleExport }; + use #wasmtime_bindings :: get_host_call_conv; + pub fn signature() -> ir::Signature { + #sig_build + sig + } + + pub struct Wrapper { + vmctx: *mut VMContext, + export: InstanceHandleExport, + } + impl Wrapper { + pub fn new(mut instance: InstanceHandle, export: InstanceHandleExport) -> Self { + Wrapper { + vmctx: instance.vmctx_mut_ptr(), + export, + } + } + pub fn call(&self, #cb_abi_params) #cb_abi_return { + type F = extern fn(#abi_params) #abi_return; + let (_f, vmctx) = #wasmtime_bindings :: get_body(&self . export); + let _f: F = unsafe { std::mem::transmute(_f) }; + #build_cb_context + #cb_params_conversion + let _res = _f(#cb_call_args); + #cb_ret_conversion + } + } + pub fn metadata() -> #wasmtime_bindings :: FnMetadata { + #wasmtime_bindings :: FnMetadata { + name: stringify!(#name), + signature: signature(), + address: super :: #name as extern fn(#abi_params) #abi_return as *const _, + } + } + } + } + } else { + quote! {} + }; + + quote! { + pub extern fn #name (#abi_params) #abi_return { + let _closure = | #inputs | #output #body; + #build_context + #params_conversion + let _res = _closure( #call_args ); + #ret_conversion + } + #def_module + } +} diff --git a/crates/bindings/macro/src/signature.rs b/crates/bindings/macro/src/signature.rs new file mode 100644 index 000000000000..8a02d6303049 --- /dev/null +++ b/crates/bindings/macro/src/signature.rs @@ -0,0 +1,115 @@ +use syn::{token, FnArg, Ident, Pat, Path, ReturnType, Signature, Type}; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub(crate) enum PtrOrRef { + Ptr, + Ref, +} + +pub(crate) enum ParameterType<'a> { + VMContextMutPtr, + SelfRef(Option), + Context(Option<(PtrOrRef, Option)>), + Ptr(&'a Type, PtrOrRef, Option), + Simple(&'a Type), +} + +pub(crate) enum Return<'a> { + Ptr(&'a Type, PtrOrRef, Option), + Simple(&'a Type), +} + +pub(crate) struct Parameter<'a> { + pub(crate) id: Option<&'a Ident>, + pub(crate) ty: ParameterType<'a>, +} + +pub(crate) struct MethodSignature<'a> { + pub(crate) params: Vec>, + pub(crate) result: Option>, + pub(crate) original_params: Vec>, + pub(crate) original_result: Option<&'a Type>, +} + +pub(crate) fn read_signature<'a>( + sig: &'a Signature, + context: &Option, +) -> MethodSignature<'a> { + let mut params = Vec::new(); + let mut original_params = Vec::new(); + for i in &sig.inputs { + match i { + FnArg::Typed(t) => { + let id = Some(if let Pat::Ident(ref id) = *t.pat { + assert!(id.attrs.is_empty()); + assert!(id.subpat.is_none()); + &id.ident + } else { + panic!("no id"); + }); + let ty = match *t.ty { + Type::Ptr(ref pt) => match *pt.elem { + Type::Path(ref p) + if p.path.is_ident("VMContext") && pt.mutability.is_some() => + { + ParameterType::VMContextMutPtr + } + Type::Path(ref p) if Some(&p.path) == context.as_ref() => { + ParameterType::Context(Some((PtrOrRef::Ptr, pt.mutability.clone()))) + } + _ => ParameterType::Ptr(&t.ty, PtrOrRef::Ptr, pt.mutability), + }, + Type::Path(ref tp) => { + if context.as_ref().map(|c| *c == tp.path) == Some(true) { + ParameterType::Context(None) + } else { + ParameterType::Simple(&t.ty) + } + } + Type::Reference(ref tr) => { + let is_context = if let Type::Path(ref tp) = *tr.elem { + context.as_ref().map(|c| *c == tp.path) == Some(true) + } else { + false + }; + if is_context { + ParameterType::Context(Some((PtrOrRef::Ref, tr.mutability.clone()))) + } else { + ParameterType::Ptr(&t.ty, PtrOrRef::Ref, tr.mutability.clone()) + } + } + _ => panic!("Unsupported param type declaration"), + }; + params.push(Parameter { id, ty }); + original_params.push(Some(&*t.ty)); + } + FnArg::Receiver(r) => { + assert!(r.attrs.is_empty()); + assert!(r.reference.is_some(), "self needs reference"); + params.push(Parameter { + id: None, + ty: ParameterType::SelfRef(r.mutability.clone().into()), + }); + original_params.push(None); + } + } + } + let (result, original_result) = if let ReturnType::Type(_, ref rt) = sig.output { + ( + Some(match **rt { + Type::Ptr(ref pt) => Return::Ptr(&**rt, PtrOrRef::Ptr, pt.mutability.clone()), + Type::Path(_) => Return::Simple(&**rt), + _ => panic!("Unsupported result type declaration"), + }), + Some(&**rt), + ) + } else { + (None, None) + }; + MethodSignature { + params, + result, + original_params, + original_result, + } +} diff --git a/crates/bindings/macro/src/trait.rs b/crates/bindings/macro/src/trait.rs new file mode 100644 index 000000000000..21181151e2ab --- /dev/null +++ b/crates/bindings/macro/src/trait.rs @@ -0,0 +1,198 @@ +use crate::attr::TransformAttributes; +use crate::method::{need_context, transform_sig, TransformSignatureResult}; +use crate::signature::{read_signature, Parameter, ParameterType}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Ident, ItemTrait, TraitItem, TraitItemMethod, Visibility}; + +fn generate_method_wrapper( + m: &TraitItemMethod, + wasm_bindings_common: TokenStream, + attr: &TransformAttributes, +) -> (TokenStream, TokenStream, TokenStream, TokenStream, Ident) { + let rsig = read_signature(&m.sig, &attr.context); + let _self_ref = match rsig.params.get(0) { + Some(Parameter { + ty: ParameterType::SelfRef(ref sr), + .. + }) => sr, + _ => { + panic!("expected first parameter to be self ref"); + } + }; + + let (build_context, build_cb_context, context_name) = if need_context(&rsig) { + if let Some(context_name) = &attr.context { + ( + quote! { + let _ctx = #context_name :: from_vmctx(vmctx); + }, + quote! { + let _ctx = #context_name :: from_vmctx(self.vmctx); + }, + quote! { #context_name }, + ) + } else { + ( + quote! { + let _ctx = #wasm_bindings_common :: VMContextWrapper(vmctx); + }, + quote! { + let _ctx = #wasm_bindings_common :: VMContextWrapper(self.vmctx); + }, + quote! { #wasm_bindings_common :: VMContextWrapper }, + ) + } + } else { + (quote! {}, quote! {}, quote! { panic!() }) + }; + + let TransformSignatureResult { + abi_params, + abi_return, + params_conversion, + ret_conversion, + call_args, + sig_build, + cb_abi_params, + cb_abi_return, + cb_params_conversion, + cb_ret_conversion, + cb_call_args, + } = transform_sig(&rsig, context_name, wasm_bindings_common.clone()); + let name = &m.sig.ident; + let result = quote! { + pub extern fn #name (#abi_params) #abi_return { + #build_context + #params_conversion + let _res = _self . #name ( #call_args ); + #ret_conversion + } + }; + let call_wrapper = quote! { + pub fn #name(&self, #cb_abi_params) #cb_abi_return { + type F = extern fn(#abi_params) #abi_return; + let (_f, vmctx) = #wasm_bindings_common :: get_body(&self . #name); + let _f: F = unsafe { std::mem::transmute(_f) }; + #build_cb_context + #cb_params_conversion + let _res = _f(#cb_call_args); + #cb_ret_conversion + } + }; + let sig_build = quote! { + pub fn #name () -> ir::Signature { + #sig_build + sig + } + }; + let fn_type = quote! { + extern fn(#abi_params) #abi_return + }; + (result, sig_build, call_wrapper, fn_type, name.clone()) +} + +pub(crate) fn wrap_trait(tr: ItemTrait, attr: TransformAttributes) -> TokenStream { + let vis = &tr.vis; + let ident = &tr.ident; + + let mod_name = attr.module.as_ref().unwrap(); + let wasmtime_bindings = quote! { :: wasmtime_bindings }; + let mut mod_wrappers = TokenStream::new(); + let mut signatures = TokenStream::new(); + let mut wrapper_fields = TokenStream::new(); + let mut wrapper_fields_init = TokenStream::new(); + let mut wrapper_impl = TokenStream::new(); + let mut metadata = TokenStream::new(); + for i in &tr.items { + if let TraitItem::Method(ref m) = i { + let (wrapper, signature, call_wrapper, fn_type, export) = + generate_method_wrapper(m, wasmtime_bindings.clone(), &attr); + mod_wrappers.extend(wrapper); + signatures.extend(signature); + + let export_name = export.to_string(); + wrapper_fields.extend(quote! { + #export: InstanceHandleExport, + }); + wrapper_fields_init.extend(quote! { + #export: instance.lookup(#export_name).unwrap(), + }); + wrapper_impl.extend(call_wrapper); + metadata.extend(quote! { + #wasmtime_bindings :: FnMetadata { + name: #export_name, + signature: signatures::#export(), + address: #export as #fn_type as *const _, + }, + }); + } + } + + let mod_item_vis = match vis { + Visibility::Inherited => quote! { pub(super) }, + vis => quote! { #vis }, + }; + let mod_content = quote! { + #vis mod #mod_name { + use super::*; + use #wasmtime_bindings :: { + VMContext, InstanceHandle, InstanceHandleExport, + AbiPrimitive, WasmMem + }; + use ::std::boxed::Box; + use ::std::cell::{Ref, RefMut, RefCell}; + type Subject = dyn super :: #ident; + + pub struct State { + #mod_item_vis subject: RefCell< + Box + >, + } + impl State { + fn from<'a>(vmctx: *mut VMContext) -> &'a mut Self { + unsafe { &mut *(&mut *vmctx).host_state().downcast_mut::().unwrap() } + } + } + #vis fn get_self(vmctx: *mut VMContext) -> Ref<'static, Subject> { + use ::core::ops::Deref; + Ref::map(State::from(vmctx).subject.borrow(), |b| b.deref()) + } + #vis fn get_self_mut(vmctx: *mut VMContext) -> RefMut<'static, Subject> { + use ::core::ops::DerefMut; + RefMut::map(State::from(vmctx).subject.borrow_mut(), |b| b.deref_mut()) + } + #mod_wrappers + + pub struct Wrapper { + #wrapper_fields + vmctx: *mut VMContext, + } + impl Wrapper { + pub fn new(mut instance: InstanceHandle) -> Self { + Wrapper { + #wrapper_fields_init + vmctx: instance.vmctx_mut_ptr(), + } + } + + #wrapper_impl + } + + pub mod signatures { + use super::*; + use #wasmtime_bindings :: codegen :: {ir, isa}; + #signatures + } + + pub fn metadata() -> Vec<#wasmtime_bindings :: FnMetadata> { + vec![#metadata] + } + } + }; + quote! { + #tr + + #mod_content + } +} diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs new file mode 100644 index 000000000000..8cb119637fed --- /dev/null +++ b/crates/bindings/src/lib.rs @@ -0,0 +1,110 @@ +pub use cranelift_codegen as codegen; +pub use wasmtime_runtime::{ + Export as InstanceHandleExport, InstanceHandle, VMContext, VMFunctionBody, +}; + +mod r#macro; + +pub trait IntoIRType { + fn into_ir_type() -> codegen::ir::Type; +} + +pub trait AbiPrimitive { + type Abi: IntoIRType; + fn convert_to_abi(self) -> Self::Abi; + fn create_from_abi(ret: Self::Abi) -> Self; +} + +macro_rules! cast32 { + ($($i:ident)*) => ($( + impl AbiPrimitive for $i { + type Abi = i32; + + fn convert_to_abi(self) -> Self::Abi { + self as i32 + } + + fn create_from_abi(p: Self::Abi) -> Self { + p as $i + } + } + )*) +} + +macro_rules! cast64 { + ($($i:ident)*) => ($( + impl AbiPrimitive for $i { + type Abi = i64; + + fn convert_to_abi(self) -> Self::Abi { + self as i64 + } + + fn create_from_abi(p: Self::Abi) -> Self { + p as $i + } + } + )*) +} + +cast32!(i8 i16 i32 u8 u16 u32); +cast64!(i64 u64); + +pub trait WasmMem { + type Abi; + fn as_ptr(&self, off: Self::Abi) -> *mut T; + fn as_off(&self, ptr: *const T) -> Self::Abi; +} + +pub struct VMContextWrapper(pub *mut VMContext); + +impl WasmMem for VMContextWrapper { + type Abi = i32; + fn as_ptr(&self, _off: Self::Abi) -> *mut T { + unimplemented!(); + } + fn as_off(&self, _ptr: *const T) -> Self::Abi { + unimplemented!(); + } +} + +impl IntoIRType for i32 { + fn into_ir_type() -> codegen::ir::Type { + codegen::ir::types::I32 + } +} + +impl IntoIRType for u32 { + fn into_ir_type() -> codegen::ir::Type { + codegen::ir::types::I32 + } +} + +impl IntoIRType for i64 { + fn into_ir_type() -> codegen::ir::Type { + codegen::ir::types::I64 + } +} + +pub fn get_ir_type() -> codegen::ir::Type { + T::into_ir_type() +} + +pub fn get_body(export: &InstanceHandleExport) -> (*const VMFunctionBody, *mut VMContext) { + // TODO check signature? + if let InstanceHandleExport::Function { address, vmctx, .. } = export { + (*address, *vmctx) + } else { + panic!("not a function export") + } +} + +pub struct FnMetadata { + pub name: &'static str, + pub signature: codegen::ir::Signature, + pub address: *const u8, +} + +pub fn get_host_call_conv() -> codegen::isa::CallConv { + codegen::isa::CallConv::triple_default(&target_lexicon::HOST) +} diff --git a/crates/bindings/src/macro.rs b/crates/bindings/src/macro.rs new file mode 100644 index 000000000000..b8edf0f04840 --- /dev/null +++ b/crates/bindings/src/macro.rs @@ -0,0 +1,17 @@ +#[macro_export] +macro_rules! wrap_wasmtime_method { + ($export:literal in $instance:expr; module( $module_name:path )) => {{ + use $module_name as m; + let mut instance = $instance.clone(); + let export = instance.lookup($export).unwrap(); + m::Wrapper::new(instance, export) + }}; +} + +#[macro_export] +macro_rules! wrap_wasmtime_instance { + ($instance:expr; module( $module_name:path )) => {{ + use $module_name as m; + m::Wrapper::new($instance) + }}; +} diff --git a/crates/wasi-common/WASI b/crates/wasi-common/WASI index 8a968f26677e..8651dfa37b92 160000 --- a/crates/wasi-common/WASI +++ b/crates/wasi-common/WASI @@ -1 +1 @@ -Subproject commit 8a968f26677e78c0bfbae7de8673702f71b7b39b +Subproject commit 8651dfa37b92d17215d1364f935ffa1ac2b77c60 diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 0b4ff60a87a5..ae42b90d1e08 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -14,6 +14,8 @@ edition = "2018" wasmtime-runtime = { path = "../runtime" } wasmtime-environ = { path = "../environ" } wasmtime-jit = { path = "../jit" } +wasmtime-bindings = { path = "../bindings" } +wasmtime-bindings-macro = { path = "../bindings/macro" } wasi-common = { path = "../wasi-common" } cranelift-codegen = { version = "0.49", features = ["enable-serde"] } cranelift-entity = { version = "0.49", features = ["enable-serde"] } diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 115bf9ab2efa..2b9e9fc258f2 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -2,5 +2,11 @@ extern crate alloc; mod instantiate; mod syscalls; +mod r#trait; +mod trait_impl; +#[macro_use] +extern crate wasmtime_bindings_macro; pub use instantiate::{instantiate_wasi, instantiate_wasi_with_context}; +pub use r#trait::{wasi_mod, Wasi, WasiMem}; +pub use trait_impl::instantiate_wasi2; diff --git a/crates/wasi/src/trait.rs b/crates/wasi/src/trait.rs new file mode 100644 index 000000000000..c0f39bae7859 --- /dev/null +++ b/crates/wasi/src/trait.rs @@ -0,0 +1,489 @@ +use std::convert::{TryFrom, TryInto}; +use wasi_common::{wasi, wasi32}; +use wasmtime_bindings::{AbiPrimitive, WasmMem}; +use wasmtime_runtime::{Export, VMContext}; + +#[wasmtime_trait(module(wasi_mod), context(WasiMem))] +pub trait Wasi { + fn args_get( + &self, + ctx: WasiMem, + argv: wasi32::uintptr_t, + argv_buf: wasi32::uintptr_t, + ) -> WasiResult; + + fn args_sizes_get( + &self, + ctx: WasiMem, + argc: wasi32::uintptr_t, + argv_buf_size: wasi32::uintptr_t, + ) -> WasiResult; + + fn clock_res_get( + &mut self, + vmctx: *mut VMContext, + clock_id: wasi::__wasi_clockid_t, + resolution: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn clock_time_get( + &mut self, + vmctx: *mut VMContext, + clock_id: wasi::__wasi_clockid_t, + precision: wasi::__wasi_timestamp_t, + time: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn environ_get( + &mut self, + vmctx: *mut VMContext, + environ: wasi32::uintptr_t, + environ_buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn environ_sizes_get( + &self, + ctx: WasiMem, + environ_count: wasi32::uintptr_t, + environ_buf_size: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_prestat_get( + &self, + ctx: WasiMem, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_prestat_dir_name( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn fd_close(&mut self, vmctx: *mut VMContext, fd: wasi::__wasi_fd_t) -> wasi::__wasi_errno_t; + + fn fd_datasync(&mut self, vmctx: *mut VMContext, fd: wasi::__wasi_fd_t) + -> wasi::__wasi_errno_t; + + fn fd_pread( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + offset: wasi::__wasi_filesize_t, + nread: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_pwrite( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + offset: wasi::__wasi_filesize_t, + nwritten: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_read( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + nread: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_renumber( + &mut self, + vmctx: *mut VMContext, + from: wasi::__wasi_fd_t, + to: wasi::__wasi_fd_t, + ) -> wasi::__wasi_errno_t; + + fn fd_seek( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filedelta_t, + whence: wasi::__wasi_whence_t, + newoffset: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_tell( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + newoffset: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_fdstat_get( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_fdstat_set_flags( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + flags: wasi::__wasi_fdflags_t, + ) -> wasi::__wasi_errno_t; + + fn fd_fdstat_set_rights( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + fs_rights_base: wasi::__wasi_rights_t, + fs_rights_inheriting: wasi::__wasi_rights_t, + ) -> wasi::__wasi_errno_t; + + fn fd_sync(&mut self, vmctx: *mut VMContext, fd: wasi::__wasi_fd_t) -> wasi::__wasi_errno_t; + + fn fd_write( + &mut self, + ctx: WasiMem, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + nwritten: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_advise( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, + advice: wasi::__wasi_advice_t, + ) -> wasi::__wasi_errno_t; + + fn fd_allocate( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, + ) -> wasi::__wasi_errno_t; + + fn path_create_directory( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn path_link( + &mut self, + vmctx: *mut VMContext, + fd0: wasi::__wasi_fd_t, + flags0: wasi::__wasi_lookupflags_t, + path0: wasi32::uintptr_t, + path_len0: wasi32::size_t, + fd1: wasi::__wasi_fd_t, + path1: wasi32::uintptr_t, + path_len1: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn path_open( + &mut self, + vmctx: *mut VMContext, + dirfd: wasi::__wasi_fd_t, + dirflags: wasi::__wasi_lookupflags_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + oflags: wasi::__wasi_oflags_t, + fs_rights_base: wasi::__wasi_rights_t, + fs_rights_inheriting: wasi::__wasi_rights_t, + fs_flags: wasi::__wasi_fdflags_t, + fd: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_readdir( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + cookie: wasi::__wasi_dircookie_t, + buf_used: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn path_readlink( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + buf_used: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn path_rename( + &mut self, + vmctx: *mut VMContext, + fd0: wasi::__wasi_fd_t, + path0: wasi32::uintptr_t, + path_len0: wasi32::size_t, + fd1: wasi::__wasi_fd_t, + path1: wasi32::uintptr_t, + path_len1: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn fd_filestat_get( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn fd_filestat_set_times( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + st_atim: wasi::__wasi_timestamp_t, + st_mtim: wasi::__wasi_timestamp_t, + fstflags: wasi::__wasi_fstflags_t, + ) -> wasi::__wasi_errno_t; + + fn fd_filestat_set_size( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + size: wasi::__wasi_filesize_t, + ) -> wasi::__wasi_errno_t; + + fn path_filestat_get( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + flags: wasi::__wasi_lookupflags_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn path_filestat_set_times( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + flags: wasi::__wasi_lookupflags_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + st_atim: wasi::__wasi_timestamp_t, + st_mtim: wasi::__wasi_timestamp_t, + fstflags: wasi::__wasi_fstflags_t, + ) -> wasi::__wasi_errno_t; + + fn path_symlink( + &mut self, + vmctx: *mut VMContext, + path0: wasi32::uintptr_t, + path_len0: wasi32::size_t, + fd: wasi::__wasi_fd_t, + path1: wasi32::uintptr_t, + path_len1: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn path_unlink_file( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn path_remove_directory( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn poll_oneoff( + &mut self, + vmctx: *mut VMContext, + in_: wasi32::uintptr_t, + out: wasi32::uintptr_t, + nsubscriptions: wasi32::size_t, + nevents: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn proc_exit(&mut self, _vmctx: *mut VMContext, rval: u32); + + fn proc_raise( + &mut self, + vmctx: *mut VMContext, + sig: wasi::__wasi_signal_t, + ) -> wasi::__wasi_errno_t; + + fn random_get( + &mut self, + vmctx: *mut VMContext, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t; + + fn sched_yield(&mut self, _vmctx: *mut VMContext) -> wasi::__wasi_errno_t; + + fn sock_recv( + &mut self, + vmctx: *mut VMContext, + sock: wasi::__wasi_fd_t, + ri_data: wasi32::uintptr_t, + ri_data_len: wasi32::size_t, + ri_flags: wasi::__wasi_riflags_t, + ro_datalen: wasi32::uintptr_t, + ro_flags: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn sock_send( + &mut self, + vmctx: *mut VMContext, + sock: wasi::__wasi_fd_t, + si_data: wasi32::uintptr_t, + si_data_len: wasi32::size_t, + si_flags: wasi::__wasi_siflags_t, + so_datalen: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t; + + fn sock_shutdown( + &mut self, + vmctx: *mut VMContext, + sock: wasi::__wasi_fd_t, + how: wasi::__wasi_sdflags_t, + ) -> wasi::__wasi_errno_t; +} + +pub struct WasiMem(*mut VMContext); + +impl WasiMem { + pub fn from_vmctx(vmctx: *mut VMContext) -> Self { + WasiMem(vmctx) + } + + pub fn require_memory<'a>(&self) -> &'a mut [u8] { + unsafe { + match (*self.0).lookup_global_export("memory") { + Some(Export::Memory { + definition, + vmctx: _, + memory: _, + }) => { + std::slice::from_raw_parts_mut((*definition).base, (*definition).current_length) + } + x => { + panic!( + "no export named \"memory\", or the export isn't a mem: {:?}", + x + ); + } + } + } + } + + pub fn get_memory<'a>(&self) -> Result<&'a mut [u8], wasi::__wasi_errno_t> { + unsafe { + match (*self.0).lookup_global_export("memory") { + Some(Export::Memory { + definition, + vmctx: _, + memory: _, + }) => Ok(std::slice::from_raw_parts_mut( + (*definition).base, + (*definition).current_length, + )), + x => { + eprintln!( + "no export named \"memory\", or the export isn't a mem: {:?}", + x + ); + Err(wasi::__WASI_ENOTSUP) + } + } + } + } +} + +impl WasmMem for WasiMem { + type Abi = wasi32::uintptr_t; + fn as_ptr(&self, off: Self::Abi) -> *mut T { + let mem = self.require_memory(); + let p = &mut mem[usize::try_from(off).unwrap()]; + p as *mut u8 as *mut T + } + fn as_off(&self, ptr: *const T) -> Self::Abi { + let mem = self.require_memory(); + let ptr = ptr as usize; + // TODO use offset from + let offset = (ptr as usize) - (mem.as_ptr() as usize); + if offset >= mem.len() { + panic!("offset out of mem boundary"); + } + offset.try_into().unwrap() + } +} + +pub struct WasiResult(wasi::__wasi_errno_t); + +impl AbiPrimitive for WasiResult { + type Abi = i32; + fn convert_to_abi(self) -> Self::Abi { + self.0.try_into().unwrap() + } + fn create_from_abi(ret: Self::Abi) -> Self { + WasiResult(ret.try_into().unwrap()) + } +} + +impl From> for WasiResult { + fn from(r: Result<(), wasi::__wasi_errno_t>) -> WasiResult { + match r { + Ok(()) => WasiResult(wasi::__WASI_ESUCCESS), + Err(v) => { + debug_assert!(v != wasi::__WASI_ESUCCESS); + WasiResult(v) + } + } + } +} + +impl From for WasiResult { + fn from(r: wasi::__wasi_errno_t) -> Self { + WasiResult(r) + } +} + +impl Into for WasiResult { + fn into(self) -> wasi::__wasi_errno_t { + self.0 + } +} + +// TODO detect nightly +#[cfg(nightly)] +impl std::ops::Try for WasiResult { + type Ok = (); + type Error = wasi::__wasi_errno_t; + fn into_result(self) -> Result { + if self.0 == wasi::__WASI_ESUCCESS { + Ok(()) + } else { + Err(self.0) + } + } + fn from_error(v: Self::Error) -> Self { + debug_assert!(v != wasi::__WASI_ESUCCESS); + WasiResult(v) + } + fn from_ok(v: Self::Ok) -> Self { + WasiResult(wasi::__WASI_ESUCCESS) + } +} diff --git a/crates/wasi/src/trait_impl.rs b/crates/wasi/src/trait_impl.rs new file mode 100644 index 000000000000..0520b837eeeb --- /dev/null +++ b/crates/wasi/src/trait_impl.rs @@ -0,0 +1,613 @@ +use crate::r#trait::wasi_mod; +use crate::r#trait::{Wasi, WasiMem, WasiResult}; + +use log::trace; +use wasi_common::{hostcalls, wasi, wasi32, WasiCtx}; +use wasmtime_runtime::VMContext; + +pub struct WasiImpl(WasiCtx); + +impl WasiImpl { + fn get_wasi_ctx(&self) -> &WasiCtx { + &self.0 + } + + fn get_wasi_ctx_mut(&mut self) -> &mut WasiCtx { + &mut self.0 + } +} + +#[allow(unused_variables)] +impl Wasi for WasiImpl { + fn args_get( + &self, + ctx: WasiMem, + argv: wasi32::uintptr_t, + argv_buf: wasi32::uintptr_t, + ) -> WasiResult { + trace!("args_get(argv={:#x?}, argv_buf={:#x?})", argv, argv_buf,); + let wasi_ctx = self.get_wasi_ctx(); + let memory = ctx.require_memory(); + unsafe { hostcalls::args_get(wasi_ctx, memory, argv, argv_buf).into() } + } + + fn args_sizes_get( + &self, + ctx: WasiMem, + argc: wasi32::uintptr_t, + argv_buf_size: wasi32::uintptr_t, + ) -> WasiResult { + trace!( + "args_sizes_get(argc={:#x?}, argv_buf_size={:#x?})", + argc, + argv_buf_size, + ); + let wasi_ctx = self.get_wasi_ctx(); + let memory = ctx.require_memory(); + unsafe { hostcalls::args_sizes_get(wasi_ctx, memory, argc, argv_buf_size).into() } + } + + fn clock_res_get( + &mut self, + vmctx: *mut VMContext, + clock_id: wasi::__wasi_clockid_t, + resolution: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! clock_res_get"); + wasi::__WASI_ENOTSUP + } + + fn clock_time_get( + &mut self, + vmctx: *mut VMContext, + clock_id: wasi::__wasi_clockid_t, + precision: wasi::__wasi_timestamp_t, + time: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! clock_time_get"); + wasi::__WASI_ENOTSUP + } + + fn environ_get( + &mut self, + vmctx: *mut VMContext, + environ: wasi32::uintptr_t, + environ_buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! environ_get"); + wasi::__WASI_ENOTSUP + } + + fn environ_sizes_get( + &self, + ctx: WasiMem, + environ_count: wasi32::uintptr_t, + environ_buf_size: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + trace!( + "environ_sizes_get(environ_count={:#x?}, environ_buf_size={:#x?})", + environ_count, + environ_buf_size, + ); + let wasi_ctx = self.get_wasi_ctx(); + let memory = ctx.require_memory(); + unsafe { hostcalls::environ_sizes_get(wasi_ctx, memory, environ_count, environ_buf_size) } + } + + fn fd_prestat_get( + &self, + ctx: WasiMem, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + trace!("fd_prestat_get(fd={:?}, buf={:#x?})", fd, buf); + let wasi_ctx = self.get_wasi_ctx(); + let memory = ctx.require_memory(); + unsafe { hostcalls::fd_prestat_get(wasi_ctx, memory, fd, buf) } + } + + fn fd_prestat_dir_name( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_prestat_dir_name"); + wasi::__WASI_ENOTSUP + } + + fn fd_close(&mut self, vmctx: *mut VMContext, fd: wasi::__wasi_fd_t) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_close"); + wasi::__WASI_ENOTSUP + } + + fn fd_datasync( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_datasync"); + wasi::__WASI_ENOTSUP + } + + fn fd_pread( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + offset: wasi::__wasi_filesize_t, + nread: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_pread"); + wasi::__WASI_ENOTSUP + } + + fn fd_pwrite( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + offset: wasi::__wasi_filesize_t, + nwritten: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_pwrite"); + wasi::__WASI_ENOTSUP + } + + fn fd_read( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + nread: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_read"); + wasi::__WASI_ENOTSUP + } + + fn fd_renumber( + &mut self, + vmctx: *mut VMContext, + from: wasi::__wasi_fd_t, + to: wasi::__wasi_fd_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_renumber"); + wasi::__WASI_ENOTSUP + } + + fn fd_seek( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filedelta_t, + whence: wasi::__wasi_whence_t, + newoffset: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_seek"); + wasi::__WASI_ENOTSUP + } + + fn fd_tell( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + newoffset: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_tell"); + wasi::__WASI_ENOTSUP + } + + fn fd_fdstat_get( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_fdstat_get"); + wasi::__WASI_ENOTSUP + } + + fn fd_fdstat_set_flags( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + flags: wasi::__wasi_fdflags_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_fdstat_set_flags"); + wasi::__WASI_ENOTSUP + } + + fn fd_fdstat_set_rights( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + fs_rights_base: wasi::__wasi_rights_t, + fs_rights_inheriting: wasi::__wasi_rights_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_fdstat_set_rights"); + wasi::__WASI_ENOTSUP + } + + fn fd_sync(&mut self, vmctx: *mut VMContext, fd: wasi::__wasi_fd_t) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_sync"); + wasi::__WASI_ENOTSUP + } + + fn fd_write( + &mut self, + ctx: WasiMem, + fd: wasi::__wasi_fd_t, + iovs: wasi32::uintptr_t, + iovs_len: wasi32::size_t, + nwritten: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + trace!( + "fd_write(fd={:?}, iovs={:#x?}, iovs_len={:?}, nwritten={:#x?})", + fd, + iovs, + iovs_len, + nwritten + ); + let wasi_ctx = self.get_wasi_ctx_mut(); + let memory = ctx.require_memory(); + unsafe { hostcalls::fd_write(wasi_ctx, memory, fd, iovs, iovs_len, nwritten) } + } + + fn fd_advise( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, + advice: wasi::__wasi_advice_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_advise"); + wasi::__WASI_ENOTSUP + } + + fn fd_allocate( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_allocate"); + wasi::__WASI_ENOTSUP + } + + fn path_create_directory( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_create_directory"); + wasi::__WASI_ENOTSUP + } + + fn path_link( + &mut self, + vmctx: *mut VMContext, + fd0: wasi::__wasi_fd_t, + flags0: wasi::__wasi_lookupflags_t, + path0: wasi32::uintptr_t, + path_len0: wasi32::size_t, + fd1: wasi::__wasi_fd_t, + path1: wasi32::uintptr_t, + path_len1: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_link"); + wasi::__WASI_ENOTSUP + } + + fn path_open( + &mut self, + vmctx: *mut VMContext, + dirfd: wasi::__wasi_fd_t, + dirflags: wasi::__wasi_lookupflags_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + oflags: wasi::__wasi_oflags_t, + fs_rights_base: wasi::__wasi_rights_t, + fs_rights_inheriting: wasi::__wasi_rights_t, + fs_flags: wasi::__wasi_fdflags_t, + fd: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_open"); + wasi::__WASI_ENOTSUP + } + + fn fd_readdir( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + cookie: wasi::__wasi_dircookie_t, + buf_used: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_readdir"); + wasi::__WASI_ENOTSUP + } + + fn path_readlink( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + buf_used: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_readlink"); + wasi::__WASI_ENOTSUP + } + + fn path_rename( + &mut self, + vmctx: *mut VMContext, + fd0: wasi::__wasi_fd_t, + path0: wasi32::uintptr_t, + path_len0: wasi32::size_t, + fd1: wasi::__wasi_fd_t, + path1: wasi32::uintptr_t, + path_len1: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_rename"); + wasi::__WASI_ENOTSUP + } + + fn fd_filestat_get( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_filestat_get"); + wasi::__WASI_ENOTSUP + } + + fn fd_filestat_set_times( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + st_atim: wasi::__wasi_timestamp_t, + st_mtim: wasi::__wasi_timestamp_t, + fstflags: wasi::__wasi_fstflags_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_filestat_set_times"); + wasi::__WASI_ENOTSUP + } + + fn fd_filestat_set_size( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + size: wasi::__wasi_filesize_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! fd_filestat_set_size"); + wasi::__WASI_ENOTSUP + } + + fn path_filestat_get( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + flags: wasi::__wasi_lookupflags_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + buf: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_filestat_get"); + wasi::__WASI_ENOTSUP + } + + fn path_filestat_set_times( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + flags: wasi::__wasi_lookupflags_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + st_atim: wasi::__wasi_timestamp_t, + st_mtim: wasi::__wasi_timestamp_t, + fstflags: wasi::__wasi_fstflags_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_filestat_set_times"); + wasi::__WASI_ENOTSUP + } + + fn path_symlink( + &mut self, + vmctx: *mut VMContext, + path0: wasi32::uintptr_t, + path_len0: wasi32::size_t, + fd: wasi::__wasi_fd_t, + path1: wasi32::uintptr_t, + path_len1: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_symlink"); + wasi::__WASI_ENOTSUP + } + + fn path_unlink_file( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_unlink_file"); + wasi::__WASI_ENOTSUP + } + + fn path_remove_directory( + &mut self, + vmctx: *mut VMContext, + fd: wasi::__wasi_fd_t, + path: wasi32::uintptr_t, + path_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! path_remove_directory"); + wasi::__WASI_ENOTSUP + } + + fn poll_oneoff( + &mut self, + vmctx: *mut VMContext, + in_: wasi32::uintptr_t, + out: wasi32::uintptr_t, + nsubscriptions: wasi32::size_t, + nevents: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! poll_oneoff"); + wasi::__WASI_ENOTSUP + } + + fn proc_exit(&mut self, _vmctx: *mut VMContext, rval: u32) {} + + fn proc_raise( + &mut self, + vmctx: *mut VMContext, + sig: wasi::__wasi_signal_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! proc_exit"); + wasi::__WASI_ENOTSUP + } + + fn random_get( + &mut self, + vmctx: *mut VMContext, + buf: wasi32::uintptr_t, + buf_len: wasi32::size_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! random_get"); + wasi::__WASI_ENOTSUP + } + + fn sched_yield(&mut self, _vmctx: *mut VMContext) -> wasi::__wasi_errno_t { + eprintln!("!!! sched_yield"); + wasi::__WASI_ENOTSUP + } + + fn sock_recv( + &mut self, + vmctx: *mut VMContext, + sock: wasi::__wasi_fd_t, + ri_data: wasi32::uintptr_t, + ri_data_len: wasi32::size_t, + ri_flags: wasi::__wasi_riflags_t, + ro_datalen: wasi32::uintptr_t, + ro_flags: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! sock_recv"); + wasi::__WASI_ENOTSUP + } + + fn sock_send( + &mut self, + vmctx: *mut VMContext, + sock: wasi::__wasi_fd_t, + si_data: wasi32::uintptr_t, + si_data_len: wasi32::size_t, + si_flags: wasi::__wasi_siflags_t, + so_datalen: wasi32::uintptr_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! sock_send"); + wasi::__WASI_ENOTSUP + } + + fn sock_shutdown( + &mut self, + vmctx: *mut VMContext, + sock: wasi::__wasi_fd_t, + how: wasi::__wasi_sdflags_t, + ) -> wasi::__wasi_errno_t { + eprintln!("!!! sock_shutdown"); + wasi::__WASI_ENOTSUP + } +} + +use cranelift_entity::PrimaryMap; +use cranelift_wasm::DefinedFuncIndex; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fs::File; +use std::rc::Rc; +use wasi_common::WasiCtxBuilder; +use wasmtime_bindings::FnMetadata; +use wasmtime_environ::{Export, Module}; +use wasmtime_runtime::{Imports, InstanceHandle, InstantiationError, VMFunctionBody}; + +pub fn instantiate_wasi2( + _prefix: &str, + global_exports: Rc>>>, + preopened_dirs: &[(String, File)], + argv: &[String], + environ: &[(String, String)], +) -> Result { + let mut module = Module::new(); + let mut finished_functions: PrimaryMap = + PrimaryMap::new(); + + for FnMetadata { + name, + signature, + address, + } in wasi_mod::metadata().into_iter() + { + let sig_id = module.signatures.push(signature); + let func_id = module.functions.push(sig_id); + module + .exports + .insert(name.to_string(), Export::Function(func_id)); + + finished_functions.push(address as *const VMFunctionBody); + } + + let imports = Imports::none(); + let data_initializers = Vec::new(); + let signatures = PrimaryMap::new(); + + let mut wasi_ctx_builder = WasiCtxBuilder::new() + .inherit_stdio() + .args(argv) + .envs(environ); + + for (dir, f) in preopened_dirs { + wasi_ctx_builder = wasi_ctx_builder.preopened_dir( + f.try_clone().map_err(|err| { + InstantiationError::Resource(format!( + "couldn't clone an instance handle to pre-opened dir: {}", + err + )) + })?, + dir, + ); + } + + let wasi_ctx = wasi_ctx_builder.build().map_err(|err| { + InstantiationError::Resource(format!("couldn't assemble WASI context object: {}", err)) + })?; + let wasi_state = wasi_mod::State { + subject: ::std::cell::RefCell::new(::std::boxed::Box::new(WasiImpl(wasi_ctx))), + }; + + InstanceHandle::new( + Rc::new(module), + global_exports, + finished_functions.into_boxed_slice(), + imports, + &data_initializers, + signatures.into_boxed_slice(), + None, + Box::new(wasi_state), + ) +} diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index f6826975fab7..9ed9e025efba 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -46,10 +46,11 @@ use wasmtime_cli::pick_compilation_strategy; use wasmtime_environ::{cache_create_new_config, cache_init}; use wasmtime_interface_types::ModuleData; use wasmtime_jit::Features; -use wasmtime_wasi::instantiate_wasi; +use wasmtime_wasi::instantiate_wasi2; +use wasmtime_wast::instantiate_spectest; + #[cfg(feature = "wasi-c")] use wasmtime_wasi_c::instantiate_wasi_c; -use wasmtime_wast::instantiate_spectest; const USAGE: &str = " Wasm runner. @@ -300,7 +301,7 @@ fn main() -> Result<()> { bail!("wasi-c feature not enabled at build time") } } else { - instantiate_wasi("", global_exports.clone(), &preopen_dirs, &argv, &environ)? + instantiate_wasi2("", global_exports.clone(), &preopen_dirs, &argv, &environ)? }; module_registry.insert(