diff --git a/Cargo.lock b/Cargo.lock index 3a90307188..cdc3486bdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,9 +1655,6 @@ dependencies = [ "tempfile", "wasi-common", "wasmtime", - "wasmtime-environ", - "wasmtime-jit", - "wasmtime-runtime", "wasmtime-wasi", "wat", ] @@ -2075,9 +2072,7 @@ dependencies = [ "target-lexicon", "wasmparser 0.47.0", "wasmtime", - "wasmtime-environ", "wasmtime-interface-types", - "wasmtime-runtime", "wasmtime-wasi", ] @@ -2123,15 +2118,10 @@ dependencies = [ name = "wasmtime-wasi" version = "0.9.0" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-wasm", + "anyhow", "log", - "target-lexicon", "wasi-common", "wasmtime", - "wasmtime-environ", - "wasmtime-jit", "wasmtime-runtime", "wig", ] diff --git a/crates/api/src/func.rs b/crates/api/src/func.rs index ff040b2b0c..41e1687284 100644 --- a/crates/api/src/func.rs +++ b/crates/api/src/func.rs @@ -45,16 +45,19 @@ macro_rules! wrappers { unsafe extern "C" fn shim( vmctx: *mut VMContext, _caller_vmctx: *mut VMContext, - $($args: $args,)* + $($args: $args::Abi,)* ) -> R::Abi where F: Fn($($args),*) -> R + 'static, + $($args: WasmArg,)* R: WasmRet, { let ret = { let instance = InstanceHandle::from_vmctx(vmctx); let func = instance.host_state().downcast_ref::().expect("state"); - panic::catch_unwind(AssertUnwindSafe(|| func($($args),*))) + panic::catch_unwind(AssertUnwindSafe(|| { + func($($args::from_abi(_caller_vmctx, $args)),*) + })) }; match ret { Ok(ret) => ret.into_abi(), @@ -166,6 +169,36 @@ impl Func { /// /// For more information about this function, see [`Func::wrap1`]. (wrap5, A, B, C, D, E) + + /// Creates a new `Func` from the given Rust closure, which takes 6 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap6, A, B, C, D, E, G) + + /// Creates a new `Func` from the given Rust closure, which takes 7 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap7, A, B, C, D, E, G, H) + + /// Creates a new `Func` from the given Rust closure, which takes 8 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap8, A, B, C, D, E, G, H, I) + + /// Creates a new `Func` from the given Rust closure, which takes 9 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap9, A, B, C, D, E, G, H, I, J) + + /// Creates a new `Func` from the given Rust closure, which takes 10 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap10, A, B, C, D, E, G, H, I, J, K) } fn from_wrapped( @@ -248,36 +281,65 @@ impl fmt::Debug for Func { /// /// For more information see [`Func::wrap1`] pub trait WasmArg { + #[doc(hidden)] + type Abi; #[doc(hidden)] fn push(dst: &mut Vec); + #[doc(hidden)] + fn from_abi(vmctx: *mut VMContext, abi: Self::Abi) -> Self; } impl WasmArg for () { + type Abi = (); fn push(_dst: &mut Vec) {} + #[inline] + fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self { + abi + } } impl WasmArg for i32 { + type Abi = Self; fn push(dst: &mut Vec) { dst.push(ValType::I32); } + #[inline] + fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self { + abi + } } impl WasmArg for i64 { + type Abi = Self; fn push(dst: &mut Vec) { dst.push(ValType::I64); } + #[inline] + fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self { + abi + } } impl WasmArg for f32 { + type Abi = Self; fn push(dst: &mut Vec) { dst.push(ValType::F32); } + #[inline] + fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self { + abi + } } impl WasmArg for f64 { + type Abi = Self; fn push(dst: &mut Vec) { dst.push(ValType::F64); } + #[inline] + fn from_abi(_vmctx: *mut VMContext, abi: Self::Abi) -> Self { + abi + } } /// A trait implemented for types which can be returned from closures passed to diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 6b04b0d806..309d9bb0ac 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -2,7 +2,6 @@ use crate::externals::Extern; use crate::module::Module; use crate::runtime::Store; use crate::trap::Trap; -use crate::types::{ExportType, ExternType}; use anyhow::{Error, Result}; use wasmtime_jit::{CompiledModule, Resolver}; use wasmtime_runtime::{Export, InstanceHandle, InstantiationError}; @@ -173,50 +172,8 @@ impl Instance { Some(&self.exports()[i]) } - #[doc(hidden)] - pub fn from_handle(store: &Store, instance_handle: InstanceHandle) -> Instance { - let mut exports = Vec::new(); - let mut exports_types = Vec::new(); - for (name, _) in instance_handle.exports() { - let export = instance_handle.lookup(name).expect("export"); - if let wasmtime_runtime::Export::Function { signature, .. } = &export { - // HACK ensure all handles, instantiated outside Store, present in - // the store's SignatureRegistry, e.g. WASI instances that are - // imported into this store using the from_handle() method. - store.compiler().signatures().register(signature); - } - - // We should support everything supported by wasmtime_runtime, or - // otherwise we've got a bug in this crate, so panic if anything - // fails to convert here. - let extern_type = match ExternType::from_wasmtime_export(&export) { - Some(ty) => ty, - None => panic!("unsupported core wasm external type {:?}", export), - }; - exports_types.push(ExportType::new(name, extern_type)); - exports.push(Extern::from_wasmtime_export( - store, - instance_handle.clone(), - export.clone(), - )); - } - - let module = Module::from_exports(store, exports_types.into_boxed_slice()); - - Instance { - instance_handle, - module, - exports: exports.into_boxed_slice(), - } - } - #[doc(hidden)] pub fn handle(&self) -> &InstanceHandle { &self.instance_handle } - - #[doc(hidden)] - pub fn get_wasmtime_memory(&self) -> Option { - self.instance_handle.lookup("memory") - } } diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index bc275c2aab..4c6ffa77e4 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -272,13 +272,6 @@ impl Module { validate(binary, Some(config)).map_err(Error::new) } - #[doc(hidden)] - pub fn from_exports(store: &Store, exports: Box<[ExportType]>) -> Self { - let mut ret = unsafe { Module::compile(store, b"\0asm\x01\0\0\0").unwrap() }; - Rc::get_mut(&mut ret.inner).unwrap().exports = exports; - return ret; - } - unsafe fn compile(store: &Store, binary: &[u8]) -> Result { let compiled = CompiledModule::new( &mut store.compiler_mut(), diff --git a/crates/api/src/types.rs b/crates/api/src/types.rs index 6c82114bba..968f62e32f 100644 --- a/crates/api/src/types.rs +++ b/crates/api/src/types.rs @@ -157,25 +157,6 @@ impl ExternType { (Table(TableType) table unwrap_table) (Memory(MemoryType) memory unwrap_memory) } - - /// Returns `None` if the sub-type fails to get converted, see documentation - /// for sub-types about what may fail. - pub(crate) fn from_wasmtime_export(export: &wasmtime_runtime::Export) -> Option { - Some(match export { - wasmtime_runtime::Export::Function { signature, .. } => { - ExternType::Func(FuncType::from_wasmtime_signature(signature.clone())?) - } - wasmtime_runtime::Export::Memory { memory, .. } => { - ExternType::Memory(MemoryType::from_wasmtime_memory(&memory.memory)) - } - wasmtime_runtime::Export::Global { global, .. } => { - ExternType::Global(GlobalType::from_wasmtime_global(&global)?) - } - wasmtime_runtime::Export::Table { table, .. } => { - ExternType::Table(TableType::from_wasmtime_table(&table.table)) - } - }) - } } // Function Types diff --git a/crates/misc/py/Cargo.toml b/crates/misc/py/Cargo.toml index 633cab3adb..2531b26484 100644 --- a/crates/misc/py/Cargo.toml +++ b/crates/misc/py/Cargo.toml @@ -18,9 +18,7 @@ doc = false [dependencies] wasmtime = { path = "../../api", version = "0.9.0" } -wasmtime-environ = { path = "../../environ", version = "0.9.0" } wasmtime-interface-types = { path = "../../interface-types", version = "0.9.0" } -wasmtime-runtime = { path = "../../runtime", version = "0.9.0" } wasmtime-wasi = { path = "../../wasi", version = "0.9.0" } target-lexicon = { version = "0.10.0", default-features = false } anyhow = "1.0.19" diff --git a/crates/misc/py/src/lib.rs b/crates/misc/py/src/lib.rs index b84975e178..b9c8d70874 100644 --- a/crates/misc/py/src/lib.rs +++ b/crates/misc/py/src/lib.rs @@ -89,9 +89,11 @@ pub fn instantiate( // If this module expects to be able to use wasi then go ahead and hook // that up into the imported crates. let wasi = if let Some(module_name) = data.find_wasi_module_name() { - let instance = wasmtime_wasi::create_wasi_instance(&store, &[], &[], &[]) + let cx = wasmtime_wasi::WasiCtxBuilder::new() + .build() .map_err(|e| err2py(e.into()))?; - Some((module_name, instance)) + let wasi = wasmtime_wasi::Wasi::new(&store, cx); + Some((module_name, wasi)) } else { None }; @@ -111,7 +113,7 @@ pub fn instantiate( .ok_or_else(|| { PyErr::new::(format!("wasi export {} is not found", i.name(),)) })?; - imports.push(e.clone()); + imports.push(e.clone().into()); } else { return Err(PyErr::new::(format!( "imported module {} is not found", diff --git a/crates/misc/rust/macro/src/lib.rs b/crates/misc/rust/macro/src/lib.rs index f97a203029..92c14ae92d 100644 --- a/crates/misc/rust/macro/src/lib.rs +++ b/crates/misc/rust/macro/src/lib.rs @@ -62,14 +62,14 @@ fn generate_load(item: &syn::ItemTrait) -> syn::Result { let mut imports: Vec = Vec::new(); if let Some(module_name) = data.find_wasi_module_name() { - let wasi_instance = #root::wasmtime_wasi::create_wasi_instance(&store, &[], &[], &[]) - .map_err(|e| format_err!("wasm instantiation error: {:?}", e))?; + let wasi_cx = #root::wasmtime_wasi::WasiCtxBuilder::new().build()?; + let wasi = #root::wasmtime_wasi::Wasi::new(&store, wasi_cx); for i in module.imports().iter() { if i.module() != module_name { bail!("unknown import module {}", i.module()); } - if let Some(export) = wasi_instance.get_export(i.name()) { - imports.push(export.clone()); + if let Some(export) = wasi.get_export(i.name()) { + imports.push(export.clone().into()); } else { bail!("unknown import {}:{}", i.module(), i.name()) } diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 28833c37ab..dc79d87f8a 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -11,9 +11,6 @@ cfg-if = "0.1.9" [dev-dependencies] wasi-common = { path = "../wasi-common", version = "0.9.0" } -wasmtime-runtime = { path = "../runtime", version = "0.9.0" } -wasmtime-environ = { path = "../environ", version = "0.9.0" } -wasmtime-jit = { path = "../jit", version = "0.9.0" } wasmtime-wasi = { path = "../wasi", version = "0.9.0" } wasmtime = { path = "../api", version = "0.9.0" } target-lexicon = "0.10.0" diff --git a/crates/test-programs/tests/wasm_tests/runtime.rs b/crates/test-programs/tests/wasm_tests/runtime.rs index 53cd3b0f20..d10f239f51 100644 --- a/crates/test-programs/tests/wasm_tests/runtime.rs +++ b/crates/test-programs/tests/wasm_tests/runtime.rs @@ -33,14 +33,7 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any // stdin is closed which causes tests to fail. let (reader, _writer) = os_pipe::pipe()?; builder = builder.stdin(reader_to_file(reader)); - let snapshot1 = Instance::from_handle( - &store, - wasmtime_wasi::instantiate_wasi_with_context( - builder.build().context("failed to build wasi context")?, - ) - .context("failed to instantiate wasi")?, - ); - + let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?); let module = Module::new(&store, &data).context("failed to create wasm module")?; let imports = module .imports() @@ -48,7 +41,7 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any .map(|i| { let field_name = i.name(); if let Some(export) = snapshot1.get_export(field_name) { - Ok(export.clone()) + Ok(export.clone().into()) } else { bail!( "import {} was not found in module {}", diff --git a/crates/wasi-common/wig/src/lib.rs b/crates/wasi-common/wig/src/lib.rs index 1bc36ba60b..d0fd3f41e0 100644 --- a/crates/wasi-common/wig/src/lib.rs +++ b/crates/wasi-common/wig/src/lib.rs @@ -34,8 +34,8 @@ pub fn witx_wasi32_types(args: TokenStream) -> TokenStream { /// A single-use macro in the `wasmtime-wasi` crate. #[proc_macro] -pub fn define_add_wrappers_to_module(args: TokenStream) -> TokenStream { - wasi::add_wrappers_to_module(args.into()).into() +pub fn define_wasi_struct(args: TokenStream) -> TokenStream { + wasi::define_struct(args.into()).into() } #[proc_macro] diff --git a/crates/wasi-common/wig/src/wasi.rs b/crates/wasi-common/wig/src/wasi.rs index 71c4369118..4e681bfc47 100644 --- a/crates/wasi-common/wig/src/wasi.rs +++ b/crates/wasi-common/wig/src/wasi.rs @@ -11,18 +11,16 @@ enum Abi { /// This is a single-use macro intended to be used in the `wasmtime-wasi` crate. /// -/// This macro will generate a function, `add_wrappers_to_module`, which will -/// use the input arguments to register all wasi functions inside of a `Module` -/// instance. This will automatically assign the wasm underlying types and -/// perform conversions from the wasm type to the underlying wasi type (often -/// unsigned or of a smaller width). +/// This macro will generate a structure, `Wasi`, which will create all the +/// functions necessary to bind wasi and hook everything up via the `wasmtime` +/// crate. /// /// The generated shim functions here will also `trace!` their arguments for /// logging purposes. Otherwise this is hopefully somewhat straightforward! /// /// I'd recommend using `cargo +nightly expand` to explore the output of this /// macro some more. -pub fn add_wrappers_to_module(args: TokenStream) -> TokenStream { +pub fn define_struct(args: TokenStream) -> TokenStream { let (path, _phase) = utils::witx_path_from_args(args); let doc = match witx::load(&[&path]) { Ok(doc) => doc, @@ -31,12 +29,18 @@ pub fn add_wrappers_to_module(args: TokenStream) -> TokenStream { } }; - let mut add = Vec::new(); + let mut fields = Vec::new(); + let mut get_exports = Vec::new(); + let mut ctor_externs = Vec::new(); + let mut ctor_fields = Vec::new(); for module in doc.modules() { for func in module.funcs() { let name = func.name.as_str(); let name_ident = Ident::new(func.name.as_str(), Span::call_site()); + fields.push(quote! { pub #name_ident: wasmtime::Func }); + get_exports.push(quote! { #name => Some(&self.#name_ident) }); + ctor_fields.push(name_ident.clone()); let mut shim_arg_decls = Vec::new(); let mut params = Vec::new(); @@ -178,56 +182,71 @@ pub fn add_wrappers_to_module(args: TokenStream) -> TokenStream { } let format_str = format!("{}({})", name, formats.join(", ")); - add.push(quote! { - let sig = module.signatures.push(translate_signature( - ir::Signature { - params: vec![#(cranelift_codegen::ir::AbiParam::new(#params)),*], - returns: vec![#(cranelift_codegen::ir::AbiParam::new(#returns)),*], - call_conv, - }, - pointer_type, - )); - let func = module.functions.push(sig); - module - .exports - .insert(#name.to_owned(), Export::Function(func)); - - unsafe extern "C" fn #name_ident( - ctx: *mut wasmtime_runtime::VMContext, - caller_ctx: *mut wasmtime_runtime::VMContext, - #(#shim_arg_decls),* - ) -> #ret_ty { - log::trace!( - #format_str, - #(#format_args),* - ); - let mut wasi_ctx = match get_wasi_ctx(&mut *ctx) { - Ok(e) => e.borrow_mut(), - Err(e) => #handle_early_error, - }; - let memory = match get_memory(&mut *caller_ctx) { - Ok(e) => e, - Err(e) => #handle_early_error, - }; - hostcalls::#name_ident( - &mut *wasi_ctx, - memory, - #(#hostcall_args),* - ) #cvt_ret - } - finished_functions.push(#name_ident as *const _); + let wrap = format_ident!("wrap{}", shim_arg_decls.len() + 1); + ctor_externs.push(quote! { + let my_cx = cx.clone(); + let #name_ident = wasmtime::Func::#wrap( + store, + move |mem: crate::WasiCallerMemory #(,#shim_arg_decls)*| -> #ret_ty { + log::trace!( + #format_str, + #(#format_args),* + ); + unsafe { + let memory = match mem.get() { + Ok(e) => e, + Err(e) => #handle_early_error, + }; + hostcalls::#name_ident( + &mut my_cx.borrow_mut(), + memory, + #(#hostcall_args),* + ) #cvt_ret + } + } + ); }); } } quote! { - pub fn add_wrappers_to_module( - module: &mut Module, - finished_functions: &mut PrimaryMap, - call_conv: isa::CallConv, - pointer_type: types::Type, - ) { - #(#add)* + /// An instantiated instance of the wasi exports. + /// + /// This represents a wasi module which can be used to instantiate other + /// wasm modules. This structure exports all that various fields of the + /// wasi instance as fields which can be used to implement your own + /// instantiation logic, if necessary. Additionally [`Wasi::get_export`] + /// can be used to do name-based resolution. + pub struct Wasi { + #(#fields,)* + } + + impl Wasi { + /// Creates a new [`Wasi`] instance. + /// + /// External values are allocated into the `store` provided and + /// configuration of the wasi instance itself should be all + /// contained in the `cx` parameter. + pub fn new(store: &wasmtime::Store, cx: WasiCtx) -> Wasi { + let cx = std::rc::Rc::new(std::cell::RefCell::new(cx)); + #(#ctor_externs)* + + Wasi { + #(#ctor_fields,)* + } + } + + /// Looks up a field called `name` in this structure, returning it + /// if found. + /// + /// This is often useful when instantiating a `wasmtime` instance + /// where name resolution often happens with strings. + pub fn get_export(&self, name: &str) -> Option<&wasmtime::Func> { + match name { + #(#get_exports,)* + _ => None, + } + } } } } diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index be3bb76c80..df45702770 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -11,16 +11,11 @@ readme = "README.md" edition = "2018" [dependencies] +anyhow = "1.0" +log = { version = "0.4.8", default-features = false } +wasi-common = { path = "../wasi-common", version = "0.9.0" } wasmtime = { path = "../api", version = "0.9.0" } wasmtime-runtime = { path = "../runtime", version = "0.9.0" } -wasmtime-environ = { path = "../environ", version = "0.9.0" } -wasmtime-jit = { path = "../jit", version = "0.9.0" } -wasi-common = { path = "../wasi-common", version = "0.9.0" } -cranelift-codegen = { version = "0.56", features = ["enable-serde"] } -cranelift-entity = { version = "0.56", features = ["enable-serde"] } -cranelift-wasm = { version = "0.56", features = ["enable-serde"] } -target-lexicon = "0.10.0" -log = { version = "0.4.8", default-features = false } wig = { path = "../wasi-common/wig", version = "0.9.2" } [badges] diff --git a/crates/wasi/src/instantiate.rs b/crates/wasi/src/instantiate.rs deleted file mode 100644 index 15f178e328..0000000000 --- a/crates/wasi/src/instantiate.rs +++ /dev/null @@ -1,131 +0,0 @@ -use cranelift_codegen::ir::types; -use cranelift_codegen::{ir, isa}; -use cranelift_entity::PrimaryMap; -use cranelift_wasm::DefinedFuncIndex; -use std::cell::RefCell; -use std::fs::File; -use std::sync::Arc; -use target_lexicon::HOST; -use wasi_common::hostcalls; -use wasi_common::wasi; -use wasi_common::{WasiCtx, WasiCtxBuilder}; -use wasmtime_environ::{translate_signature, Export, Module}; -use wasmtime_runtime::{Imports, InstanceHandle, InstantiationError, VMContext}; - -/// Creates `wasmtime::Instance` object implementing the "wasi" interface. -pub fn create_wasi_instance( - store: &wasmtime::Store, - preopened_dirs: &[(String, File)], - argv: &[String], - environ: &[(String, String)], -) -> Result { - let wasi = instantiate_wasi(preopened_dirs, argv, environ)?; - let instance = wasmtime::Instance::from_handle(&store, wasi); - Ok(instance) -} - -/// Return an instance implementing the "wasi" interface. -pub fn instantiate_wasi( - preopened_dirs: &[(String, File)], - argv: &[String], - environ: &[(String, String)], -) -> Result { - 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)) - })?; - instantiate_wasi_with_context(wasi_ctx) -} - -/// Return an instance implementing the "wasi" interface. -/// -/// The wasi context is configured by -pub fn instantiate_wasi_with_context( - wasi_ctx: WasiCtx, -) -> Result { - let pointer_type = types::Type::triple_pointer_type(&HOST); - let mut module = Module::new(); - let mut finished_functions = PrimaryMap::new(); - let call_conv = isa::CallConv::triple_default(&HOST); - - // This function is defined in the macro invocation of - // `define_add_wrappers_to_module` below. For more information about how - // this works it'd recommended to read the source in - // `crates/wasi-common/wig/src/wasi.rs`. - add_wrappers_to_module( - &mut module, - &mut finished_functions, - call_conv, - pointer_type, - ); - - let imports = Imports::none(); - let data_initializers = Vec::new(); - let signatures = PrimaryMap::new(); - - unsafe { - InstanceHandle::new( - Arc::new(module), - finished_functions.into_boxed_slice(), - imports, - &data_initializers, - signatures.into_boxed_slice(), - None, - Box::new(RefCell::new(wasi_ctx)), - ) - } -} - -wig::define_add_wrappers_to_module!( - "snapshot" "wasi_snapshot_preview1" -); - -// Used by `add_wrappers_to_module` defined in the macro above -fn get_wasi_ctx(vmctx: &mut VMContext) -> Result<&RefCell, wasi::__wasi_errno_t> { - unsafe { - vmctx - .host_state() - .downcast_ref() - .ok_or_else(|| panic!("no host state named WasiCtx available")) - } -} - -// Used by `add_wrappers_to_module` defined in the macro above -fn get_memory(caller_vmctx: &mut VMContext) -> Result<&mut [u8], wasi::__wasi_errno_t> { - match unsafe { InstanceHandle::from_vmctx(caller_vmctx) }.lookup("memory") { - Some(wasmtime_runtime::Export::Memory { - definition, - vmctx: _, - memory: _, - }) => unsafe { - let definition = &*definition; - let ptr = definition.base; - let len = definition.current_length; - Ok(std::slice::from_raw_parts_mut(ptr, len)) - }, - Some(export) => { - log::error!("export named \"memory\" isn't a memory: {:?}", export); - Err(wasi::__WASI_ERRNO_INVAL) - } - None => { - log::error!("no export named \"memory\" available from caller"); - Err(wasi::__WASI_ERRNO_INVAL) - } - } -} diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 565958f9e8..d09ee7c1f8 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -1,7 +1,14 @@ -mod instantiate; pub mod old; -pub use instantiate::{create_wasi_instance, instantiate_wasi, instantiate_wasi_with_context}; +use wasi_common::hostcalls; + +pub use wasi_common::{WasiCtx, WasiCtxBuilder}; + +// Defines a `struct Wasi` with member fields and appropriate APIs for dealing +// with all the various WASI exports. +wig::define_wasi_struct!( + "snapshot" "wasi_snapshot_preview1" +); pub fn is_wasi_module(name: &str) -> bool { // FIXME: this should be more conservative, but while WASI is in flux and @@ -9,3 +16,54 @@ pub fn is_wasi_module(name: &str) -> bool { // trick. name.starts_with("wasi") } + +/// This is an internal structure used to acquire a handle on the caller's +/// wasm memory buffer. +/// +/// This exploits how we can implement `WasmArg` for ourselves locally even +/// though crates in general should not be doing that. This is a crate in +/// the wasmtime project, however, so we should be able to keep up with our own +/// changes. +/// +/// In general this type is wildly unsafe. We need to update the wasi crates to +/// probably work with more `wasmtime`-like APIs to grip with the unsafety +/// around dealing with caller memory. +struct WasiCallerMemory { + base: *mut u8, + len: usize, +} + +impl wasmtime::WasmArg for WasiCallerMemory { + type Abi = (); + + fn push(_dst: &mut Vec) {} + + fn from_abi(vmctx: *mut wasmtime_runtime::VMContext, _abi: ()) -> Self { + unsafe { + match wasmtime_runtime::InstanceHandle::from_vmctx(vmctx).lookup("memory") { + Some(wasmtime_runtime::Export::Memory { + definition, + vmctx: _, + memory: _, + }) => WasiCallerMemory { + base: (*definition).base, + len: (*definition).current_length, + }, + _ => WasiCallerMemory { + base: std::ptr::null_mut(), + len: 0, + }, + } + } + } +} + +impl WasiCallerMemory { + unsafe fn get(&self) -> Result<&mut [u8], wasi_common::wasi::__wasi_errno_t> { + if self.base.is_null() { + Err(wasi_common::wasi::__WASI_ERRNO_INVAL) + } else { + Ok(std::slice::from_raw_parts_mut(self.base, self.len)) + } + } +} diff --git a/crates/wasi/src/old/snapshot_0.rs b/crates/wasi/src/old/snapshot_0.rs new file mode 100644 index 0000000000..23cc8e1b28 --- /dev/null +++ b/crates/wasi/src/old/snapshot_0.rs @@ -0,0 +1,15 @@ +use wasi_common::old::snapshot_0::hostcalls; +use wasi_common::old::snapshot_0::WasiCtx; + +// Defines a `struct Wasi` with member fields and appropriate APIs for dealing +// with all the various WASI exports. +wig::define_wasi_struct!( + "old/snapshot_0" "wasi_unstable" +); + +pub fn is_wasi_module(name: &str) -> bool { + // FIXME: this should be more conservative, but while WASI is in flux and + // we're figuring out how to support multiple revisions, this should do the + // trick. + name.starts_with("wasi") +} diff --git a/crates/wasi/src/old/snapshot_0/instantiate.rs b/crates/wasi/src/old/snapshot_0/instantiate.rs deleted file mode 100644 index 579957c0a9..0000000000 --- a/crates/wasi/src/old/snapshot_0/instantiate.rs +++ /dev/null @@ -1,131 +0,0 @@ -use cranelift_codegen::ir::types; -use cranelift_codegen::{ir, isa}; -use cranelift_entity::PrimaryMap; -use cranelift_wasm::DefinedFuncIndex; -use std::cell::RefCell; -use std::fs::File; -use std::sync::Arc; -use target_lexicon::HOST; -use wasi_common::old::snapshot_0::hostcalls; -use wasi_common::old::snapshot_0::wasi; -use wasi_common::old::snapshot_0::{WasiCtx, WasiCtxBuilder}; -use wasmtime_environ::{translate_signature, Export, Module}; -use wasmtime_runtime::{Imports, InstanceHandle, InstantiationError, VMContext}; - -/// Creates `wasmtime::Instance` object implementing the "wasi" interface. -pub fn create_wasi_instance( - store: &wasmtime::Store, - preopened_dirs: &[(String, File)], - argv: &[String], - environ: &[(String, String)], -) -> Result { - let wasi = instantiate_wasi(preopened_dirs, argv, environ)?; - let instance = wasmtime::Instance::from_handle(&store, wasi); - Ok(instance) -} - -/// Return an instance implementing the "wasi" interface. -pub fn instantiate_wasi( - preopened_dirs: &[(String, File)], - argv: &[String], - environ: &[(String, String)], -) -> Result { - 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)) - })?; - instantiate_wasi_with_context(wasi_ctx) -} - -/// Return an instance implementing the "wasi" interface. -/// -/// The wasi context is configured by -pub fn instantiate_wasi_with_context( - wasi_ctx: WasiCtx, -) -> Result { - let pointer_type = types::Type::triple_pointer_type(&HOST); - let mut module = Module::new(); - let mut finished_functions = PrimaryMap::new(); - let call_conv = isa::CallConv::triple_default(&HOST); - - // This function is defined in the macro invocation of - // `define_add_wrappers_to_module` below. For more information about how - // this works it'd recommended to read the source in - // `crates/wasi-common/wig/src/wasi.rs`. - add_wrappers_to_module( - &mut module, - &mut finished_functions, - call_conv, - pointer_type, - ); - - let imports = Imports::none(); - let data_initializers = Vec::new(); - let signatures = PrimaryMap::new(); - - unsafe { - InstanceHandle::new( - Arc::new(module), - finished_functions.into_boxed_slice(), - imports, - &data_initializers, - signatures.into_boxed_slice(), - None, - Box::new(RefCell::new(wasi_ctx)), - ) - } -} - -// Used by `add_wrappers_to_module` defined in the macro above -fn get_wasi_ctx(vmctx: &mut VMContext) -> Result<&RefCell, wasi::__wasi_errno_t> { - unsafe { - vmctx - .host_state() - .downcast_ref() - .ok_or_else(|| panic!("no host state named WasiCtx available")) - } -} - -// Used by `add_wrappers_to_module` defined in the macro above -fn get_memory(caller_vmctx: &mut VMContext) -> Result<&mut [u8], wasi::__wasi_errno_t> { - match unsafe { InstanceHandle::from_vmctx(caller_vmctx) }.lookup("memory") { - Some(wasmtime_runtime::Export::Memory { - definition, - vmctx: _, - memory: _, - }) => unsafe { - let definition = &*definition; - let ptr = definition.base; - let len = definition.current_length; - Ok(std::slice::from_raw_parts_mut(ptr, len)) - }, - Some(export) => { - log::error!("export named \"memory\" isn't a memory: {:?}", export); - Err(wasi::__WASI_ERRNO_INVAL) - } - None => { - log::error!("no export named \"memory\" available from caller"); - Err(wasi::__WASI_ERRNO_INVAL) - } - } -} - -wig::define_add_wrappers_to_module!( - "old/snapshot_0" "wasi_unstable" -); diff --git a/crates/wasi/src/old/snapshot_0/mod.rs b/crates/wasi/src/old/snapshot_0/mod.rs deleted file mode 100644 index 5a5a14f95f..0000000000 --- a/crates/wasi/src/old/snapshot_0/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate alloc; - -mod instantiate; - -pub use instantiate::{create_wasi_instance, instantiate_wasi, instantiate_wasi_with_context}; - -pub fn is_wasi_module(name: &str) -> bool { - // FIXME: this should be more conservative, but while WASI is in flux and - // we're figuring out how to support multiple revisions, this should do the - // trick. - name.starts_with("wasi") -} diff --git a/src/commands/run.rs b/src/commands/run.rs index ca095cf2fa..038672758e 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -3,7 +3,6 @@ use crate::{init_file_per_thread_logger, pick_compilation_strategy, CommonOptions}; use anyhow::{bail, Context as _, Result}; use std::{ - collections::HashMap, ffi::{OsStr, OsString}, fmt::Write, fs::File, @@ -14,9 +13,7 @@ use wasi_common::preopen_dir; use wasmtime::{Config, Engine, Instance, Module, Store}; use wasmtime_environ::cache_init; use wasmtime_interface_types::ModuleData; -use wasmtime_wasi::{ - create_wasi_instance, old::snapshot_0::create_wasi_instance as create_wasi_instance_snapshot_0, -}; +use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi}; fn parse_module(s: &OsStr) -> Result { // Do not accept wasmtime subcommand names as the module name @@ -134,20 +131,12 @@ impl RunCommand { let engine = Engine::new(&config); let store = Store::new(&engine); - let mut module_registry = HashMap::new(); // Make wasi available by default. let preopen_dirs = self.compute_preopen_dirs()?; let argv = self.compute_argv(); - let wasi_unstable = - create_wasi_instance_snapshot_0(&store, &preopen_dirs, &argv, &self.vars)?; - - let wasi_snapshot_preview1 = - create_wasi_instance(&store, &preopen_dirs, &argv, &self.vars)?; - - module_registry.insert("wasi_unstable".to_owned(), wasi_unstable); - module_registry.insert("wasi_snapshot_preview1".to_owned(), wasi_snapshot_preview1); + let module_registry = ModuleRegistry::new(&store, &preopen_dirs, &argv, &self.vars)?; // Load the preload wasm modules. for preload in self.preloads.iter() { @@ -208,7 +197,7 @@ impl RunCommand { fn instantiate_module( store: &Store, - module_registry: &HashMap, + module_registry: &ModuleRegistry, path: &Path, ) -> Result<(Instance, Module, Vec)> { // Read the wasm module binary either as `*.wat` or a raw binary @@ -221,20 +210,20 @@ impl RunCommand { .imports() .iter() .map(|i| { - let module_name = i.module(); - if let Some(instance) = module_registry.get(module_name) { - let field_name = i.name(); - if let Some(export) = instance.get_export(field_name) { - Ok(export.clone()) - } else { - bail!( - "Import {} was not found in module {}", - field_name, - module_name - ) + let export = match i.module() { + "wasi_snapshot_preview1" => { + module_registry.wasi_snapshot_preview1.get_export(i.name()) } - } else { - bail!("Import module {} was not found", module_name) + "wasi_unstable" => module_registry.wasi_unstable.get_export(i.name()), + other => bail!("import module `{}` was not found", other), + }; + match export { + Some(export) => Ok(export.clone().into()), + None => bail!( + "import `{}` was not found in module `{}`", + i.name(), + i.module() + ), } }) .collect::, _>>()?; @@ -245,11 +234,7 @@ impl RunCommand { Ok((instance, module, data)) } - fn handle_module( - &self, - store: &Store, - module_registry: &HashMap, - ) -> Result<()> { + fn handle_module(&self, store: &Store, module_registry: &ModuleRegistry) -> Result<()> { let (instance, module, data) = Self::instantiate_module(store, module_registry, &self.module)?; @@ -337,3 +322,40 @@ impl RunCommand { Ok(()) } } + +struct ModuleRegistry { + wasi_snapshot_preview1: Wasi, + wasi_unstable: WasiSnapshot0, +} + +impl ModuleRegistry { + fn new( + store: &Store, + preopen_dirs: &[(String, File)], + argv: &[String], + vars: &[(String, String)], + ) -> Result { + let mut cx1 = wasi_common::WasiCtxBuilder::new() + .inherit_stdio() + .args(argv) + .envs(vars); + for (name, file) in preopen_dirs { + cx1 = cx1.preopened_dir(file.try_clone()?, name); + } + let cx1 = cx1.build()?; + + let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new() + .inherit_stdio() + .args(argv) + .envs(vars); + for (name, file) in preopen_dirs { + cx2 = cx2.preopened_dir(file.try_clone()?, name); + } + let cx2 = cx2.build()?; + + Ok(ModuleRegistry { + wasi_snapshot_preview1: Wasi::new(store, cx1), + wasi_unstable: WasiSnapshot0::new(store, cx2), + }) + } +} diff --git a/tests/custom_signal_handler.rs b/tests/custom_signal_handler.rs index b21054b08d..85f9af3651 100644 --- a/tests/custom_signal_handler.rs +++ b/tests/custom_signal_handler.rs @@ -49,21 +49,9 @@ mod tests { // Locate "memory" export, get base address and size and set memory protection to PROT_NONE fn set_up_memory(instance: &Instance) -> (*mut u8, usize) { - let mem_export = instance.get_wasmtime_memory().expect("memory"); - - let (base, length) = if let wasmtime_runtime::Export::Memory { - definition, - vmctx: _, - memory: _, - } = mem_export - { - unsafe { - let definition = std::ptr::read(definition); - (definition.base, definition.current_length) - } - } else { - panic!("expected memory"); - }; + let mem_export = instance.get_export("memory").unwrap().memory().unwrap(); + let base = mem_export.data_ptr(); + let length = mem_export.data_size(); // So we can later trigger SIGSEGV by performing a read unsafe {