diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs index e1078b6fa96c..b70c327534cc 100644 --- a/crates/component-macro/src/component.rs +++ b/crates/component-macro/src/component.rs @@ -353,7 +353,7 @@ fn expand_record_for_component_type( #[inline] fn typecheck( ty: &#internal::InterfaceType, - types: &#internal::ComponentTypes, + types: &#internal::InstanceType<'_>, ) -> #internal::anyhow::Result<()> { #internal::#typecheck(ty, types, &[#typecheck_argument]) } @@ -422,7 +422,7 @@ impl Expander for LiftExpander { unsafe impl #impl_generics wasmtime::component::Lift for #name #ty_generics #where_clause { #[inline] fn lift( - cx: &#internal::LiftContext<'_>, + cx: &mut #internal::LiftContext<'_>, ty: #internal::InterfaceType, src: &Self::Lower, ) -> #internal::anyhow::Result { @@ -434,7 +434,7 @@ impl Expander for LiftExpander { #[inline] fn load( - cx: &#internal::LiftContext<'_>, + cx: &mut #internal::LiftContext<'_>, ty: #internal::InterfaceType, bytes: &[u8], ) -> #internal::anyhow::Result { @@ -525,7 +525,7 @@ impl Expander for LiftExpander { unsafe impl #impl_generics wasmtime::component::Lift for #name #ty_generics #where_clause { #[inline] fn lift( - cx: &#internal::LiftContext<'_>, + cx: &mut #internal::LiftContext<'_>, ty: #internal::InterfaceType, src: &Self::Lower, ) -> #internal::anyhow::Result { @@ -538,7 +538,7 @@ impl Expander for LiftExpander { #[inline] fn load( - cx: &#internal::LiftContext<'_>, + cx: &mut #internal::LiftContext<'_>, ty: #internal::InterfaceType, bytes: &[u8], ) -> #internal::anyhow::Result { @@ -888,7 +888,7 @@ impl Expander for ComponentTypeExpander { #[inline] fn typecheck( ty: &#internal::InterfaceType, - types: &#internal::ComponentTypes, + types: &#internal::InstanceType<'_>, ) -> #internal::anyhow::Result<()> { #internal::#typecheck(ty, types, &[#case_names_and_checks]) } @@ -1312,7 +1312,7 @@ pub fn expand_flags(flags: &Flags) -> Result { unsafe impl wasmtime::component::Lift for #name { fn lift( - cx: &#internal::LiftContext<'_>, + cx: &mut #internal::LiftContext<'_>, _ty: #internal::InterfaceType, src: &Self::Lower, ) -> #internal::anyhow::Result { @@ -1328,7 +1328,7 @@ pub fn expand_flags(flags: &Flags) -> Result { } fn load( - cx: &#internal::LiftContext<'_>, + cx: &mut #internal::LiftContext<'_>, _ty: #internal::InterfaceType, bytes: &[u8], ) -> #internal::anyhow::Result { diff --git a/crates/cranelift-shared/src/lib.rs b/crates/cranelift-shared/src/lib.rs index 42a26352ca07..add380288703 100644 --- a/crates/cranelift-shared/src/lib.rs +++ b/crates/cranelift-shared/src/lib.rs @@ -56,9 +56,17 @@ fn to_flag_value(v: &settings::Value) -> FlagValue { /// Trap code used for debug assertions we emit in our JIT code. const DEBUG_ASSERT_TRAP_CODE: u16 = u16::MAX; -/// Code used as the user-defined trap code. +/// A custom code with `TrapCode::User` which is used by always-trap shims which +/// indicates that, as expected, the always-trapping function indeed did trap. +/// This effectively provides a better error message as opposed to a bland +/// "unreachable code reached" pub const ALWAYS_TRAP_CODE: u16 = 100; +/// A custom code with `TrapCode::User` corresponding to being unable to reenter +/// a component due to its reentrance limitations. This is used in component +/// adapters to provide a more useful error message in such situations. +pub const CANNOT_ENTER_CODE: u16 = 101; + /// Converts machine traps to trap information. pub fn mach_trap_to_trap(trap: &MachTrap) -> Option { let &MachTrap { offset, code } = trap; @@ -77,6 +85,7 @@ pub fn mach_trap_to_trap(trap: &MachTrap) -> Option { ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached, ir::TrapCode::Interrupt => Trap::Interrupt, ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter, + ir::TrapCode::User(CANNOT_ENTER_CODE) => Trap::CannotEnterComponent, ir::TrapCode::NullReference => Trap::NullReference, // These do not get converted to wasmtime traps, since they diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 027c7a51f9eb..3f6aa8dffbd4 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -351,7 +351,6 @@ impl wasmtime_environ::Compiler for Compiler { fn compile_wasm_to_native_trampoline( &self, - translation: &ModuleTranslation, wasm_func_ty: &WasmFuncType, ) -> Result, CompileError> { let isa = &*self.isa; @@ -379,14 +378,14 @@ impl wasmtime_environ::Compiler for Compiler { caller_vmctx, wasmtime_environ::VMCONTEXT_MAGIC, ); - let offsets = VMOffsets::new(isa.pointer_bytes(), &translation.module); + let ptr = isa.pointer_bytes(); let limits = builder.ins().load( pointer_type, MemFlags::trusted(), caller_vmctx, - i32::try_from(offsets.vmctx_runtime_limits()).unwrap(), + i32::try_from(ptr.vmcontext_runtime_limits()).unwrap(), ); - save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &offsets.ptr, limits); + save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &ptr, limits); // If the native call signature for this function uses a return pointer // then allocate the return pointer here on the stack and pass it as the diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 75b7a249fd83..6b9702d26547 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -6,12 +6,9 @@ use cranelift_codegen::ir::{self, InstBuilder, MemFlags}; use cranelift_codegen::isa::CallConv; use cranelift_frontend::FunctionBuilder; use std::any::Any; -use wasmtime_cranelift_shared::ALWAYS_TRAP_CODE; -use wasmtime_environ::component::{ - AllCallFunc, CanonicalOptions, Component, ComponentCompiler, ComponentTypes, FixedEncoding, - LowerImport, RuntimeMemoryIndex, Transcode, Transcoder, TypeDef, VMComponentOffsets, -}; -use wasmtime_environ::{PtrSize, WasmFuncType}; +use wasmtime_cranelift_shared::{ALWAYS_TRAP_CODE, CANNOT_ENTER_CODE}; +use wasmtime_environ::component::*; +use wasmtime_environ::{PtrSize, WasmFuncType, WasmType}; #[derive(Copy, Clone)] enum Abi { @@ -94,10 +91,7 @@ impl Compiler { )); // ty: TypeFuncIndex, - let ty = match component.type_of_import(lowering.import, types) { - TypeDef::ComponentFunc(func) => func, - _ => unreachable!(), - }; + let ty = lowering.lower_ty; host_sig.params.push(ir::AbiParam::new(ir::types::I32)); callee_args.push(builder.ins().iconst(ir::types::I32, i64::from(ty.as_u32()))); @@ -231,6 +225,280 @@ impl Compiler { Ok(Box::new(compiler.finish()?)) } + fn compile_resource_new_for_abi( + &self, + component: &Component, + resource: &ResourceNew, + types: &ComponentTypes, + abi: Abi, + ) -> Result> { + let ty = &types[resource.signature]; + let isa = &*self.isa; + let offsets = VMComponentOffsets::new(isa.pointer_bytes(), component); + let mut compiler = self.function_compiler(); + let func = self.func(ty, abi); + let (mut builder, block0) = compiler.builder(func); + + let args = self.abi_load_params(&mut builder, ty, block0, abi); + let vmctx = args[0]; + + self.abi_preamble(&mut builder, &offsets, vmctx, abi); + + // The arguments this shim passes along to the libcall are: + // + // * the vmctx + // * a constant value for this `ResourceNew` intrinsic + // * the wasm argument to wrap + let mut host_args = Vec::new(); + host_args.push(vmctx); + host_args.push( + builder + .ins() + .iconst(ir::types::I32, i64::from(resource.resource.as_u32())), + ); + host_args.push(args[2]); + + // Currently this only support resources represented by `i32` + assert_eq!(ty.params()[0], WasmType::I32); + let (host_sig, offset) = host::resource_new32(self, &mut builder.func); + + let host_fn = self.load_libcall(&mut builder, &offsets, vmctx, offset); + let call = builder.ins().call_indirect(host_sig, host_fn, &host_args); + let result = builder.func.dfg.inst_results(call)[0]; + self.abi_store_results(&mut builder, ty, block0, &[result], abi); + + builder.finalize(); + Ok(Box::new(compiler.finish()?)) + } + + fn compile_resource_rep_for_abi( + &self, + component: &Component, + resource: &ResourceRep, + types: &ComponentTypes, + abi: Abi, + ) -> Result> { + let ty = &types[resource.signature]; + let isa = &*self.isa; + let offsets = VMComponentOffsets::new(isa.pointer_bytes(), component); + let mut compiler = self.function_compiler(); + let func = self.func(ty, abi); + let (mut builder, block0) = compiler.builder(func); + + let args = self.abi_load_params(&mut builder, ty, block0, abi); + let vmctx = args[0]; + + self.abi_preamble(&mut builder, &offsets, vmctx, abi); + + // The arguments this shim passes along to the libcall are: + // + // * the vmctx + // * a constant value for this `ResourceRep` intrinsic + // * the wasm argument to unwrap + let mut host_args = Vec::new(); + host_args.push(vmctx); + host_args.push( + builder + .ins() + .iconst(ir::types::I32, i64::from(resource.resource.as_u32())), + ); + host_args.push(args[2]); + + // Currently this only support resources represented by `i32` + assert_eq!(ty.returns()[0], WasmType::I32); + let (host_sig, offset) = host::resource_rep32(self, &mut builder.func); + + let host_fn = self.load_libcall(&mut builder, &offsets, vmctx, offset); + let call = builder.ins().call_indirect(host_sig, host_fn, &host_args); + let result = builder.func.dfg.inst_results(call)[0]; + self.abi_store_results(&mut builder, ty, block0, &[result], abi); + + builder.finalize(); + Ok(Box::new(compiler.finish()?)) + } + + fn compile_resource_drop_for_abi( + &self, + component: &Component, + resource: &ResourceDrop, + types: &ComponentTypes, + abi: Abi, + ) -> Result> { + let pointer_type = self.isa.pointer_type(); + let ty = &types[resource.signature]; + let isa = &*self.isa; + let offsets = VMComponentOffsets::new(isa.pointer_bytes(), component); + let mut compiler = self.function_compiler(); + let func = self.func(ty, abi); + let (mut builder, block0) = compiler.builder(func); + + let args = self.abi_load_params(&mut builder, ty, block0, abi); + let vmctx = args[0]; + let caller_vmctx = args[1]; + + self.abi_preamble(&mut builder, &offsets, vmctx, abi); + + // The arguments this shim passes along to the libcall are: + // + // * the vmctx + // * a constant value for this `ResourceDrop` intrinsic + // * the wasm handle index to drop + let mut host_args = Vec::new(); + host_args.push(vmctx); + host_args.push( + builder + .ins() + .iconst(ir::types::I32, i64::from(resource.resource.as_u32())), + ); + host_args.push(args[2]); + + let (host_sig, offset) = host::resource_drop(self, &mut builder.func); + let host_fn = self.load_libcall(&mut builder, &offsets, vmctx, offset); + let call = builder.ins().call_indirect(host_sig, host_fn, &host_args); + let should_run_destructor = builder.func.dfg.inst_results(call)[0]; + + let resource_ty = types[resource.resource].ty; + let resource_def = component.defined_resource_index(resource_ty).map(|idx| { + component + .initializers + .iter() + .filter_map(|i| match i { + GlobalInitializer::Resource(r) if r.index == idx => Some(r), + _ => None, + }) + .next() + .unwrap() + }); + let has_destructor = match resource_def { + Some(def) => def.dtor.is_some(), + None => true, + }; + // Synthesize the following: + // + // ... + // brif should_run_destructor, run_destructor_block, return_block + // + // run_destructor_block: + // ;; test may_enter, but only if the component instances + // ;; differ + // flags = load.i32 vmctx+$offset + // masked = band flags, $FLAG_MAY_ENTER + // trapz masked, CANNOT_ENTER_CODE + // + // ;; ============================================================ + // ;; this is conditionally emitted based on whether the resource + // ;; has a destructor or not, and can be statically omitted + // ;; because that information is known at compile time here. + // rep = ushr.i64 rep, 1 + // rep = ireduce.i32 rep + // dtor = load.ptr vmctx+$offset + // func_addr = load.ptr dtor+$offset + // callee_vmctx = load.ptr dtor+$offset + // call_indirect func_addr, callee_vmctx, vmctx, rep + // ;; ============================================================ + // + // jump return_block + // + // return_block: + // return + // + // This will decode `should_run_destructor` and run the destructor + // funcref if one is specified for this resource. Note that not all + // resources have destructors, hence the null check. + builder.ensure_inserted_block(); + let current_block = builder.current_block().unwrap(); + let run_destructor_block = builder.create_block(); + builder.insert_block_after(run_destructor_block, current_block); + let return_block = builder.create_block(); + builder.insert_block_after(return_block, run_destructor_block); + + builder.ins().brif( + should_run_destructor, + run_destructor_block, + &[], + return_block, + &[], + ); + + let trusted = ir::MemFlags::trusted().with_readonly(); + + builder.switch_to_block(run_destructor_block); + + // If this is a defined resource within the component itself then a + // check needs to be emitted for the `may_enter` flag. Note though + // that this check can be elided if the resource table resides in + // the same component instance that defined the resource as the + // component is calling itself. + if let Some(def) = resource_def { + if types[resource.resource].instance != def.instance { + let flags = builder.ins().load( + ir::types::I32, + trusted, + vmctx, + i32::try_from(offsets.instance_flags(def.instance)).unwrap(), + ); + let masked = builder.ins().band_imm(flags, i64::from(FLAG_MAY_ENTER)); + builder + .ins() + .trapz(masked, ir::TrapCode::User(CANNOT_ENTER_CODE)); + } + } + + // Conditionally emit destructor-execution code based on whether we + // statically know that a destructor exists or not. + if has_destructor { + let rep = builder.ins().ushr_imm(should_run_destructor, 1); + let rep = builder.ins().ireduce(ir::types::I32, rep); + let index = types[resource.resource].ty; + // NB: despite the vmcontext storing nullable funcrefs for function + // pointers we know this is statically never null due to the + // `has_destructor` check above. + let dtor_func_ref = builder.ins().load( + pointer_type, + trusted, + vmctx, + i32::try_from(offsets.resource_destructor(index)).unwrap(), + ); + if cfg!(debug_assertions) { + builder.ins().trapz( + dtor_func_ref, + ir::TrapCode::User(crate::DEBUG_ASSERT_TRAP_CODE), + ); + } + let func_addr = builder.ins().load( + pointer_type, + trusted, + dtor_func_ref, + i32::from(offsets.ptr.vm_func_ref_wasm_call()), + ); + let callee_vmctx = builder.ins().load( + pointer_type, + trusted, + dtor_func_ref, + i32::from(offsets.ptr.vm_func_ref_vmctx()), + ); + let sig = crate::wasm_call_signature(isa, &types[resource.signature]); + let sig_ref = builder.import_signature(sig); + // NB: note that the "caller" vmctx here is the caller of this + // intrinsic itself, not the `VMComponentContext`. This effectively + // takes ourselves out of the chain here but that's ok since the + // caller is only used for store/limits and that same info is + // stored, but elsewhere, in the component context. + builder + .ins() + .call_indirect(sig_ref, func_addr, &[callee_vmctx, caller_vmctx, rep]); + } + builder.ins().jump(return_block, &[]); + builder.seal_block(run_destructor_block); + + builder.switch_to_block(return_block); + builder.ins().return_(&[]); + builder.seal_block(return_block); + + builder.finalize(); + Ok(Box::new(compiler.finish()?)) + } + fn func(&self, ty: &WasmFuncType, abi: Abi) -> ir::Function { let isa = &*self.isa; ir::Function::with_name_signature( @@ -254,6 +522,95 @@ impl Compiler { }) } + /// Loads a host function pointer for a libcall stored at the `offset` + /// provided in the libcalls array. + /// + /// The offset is calculated in the `host` module below. + fn load_libcall( + &self, + builder: &mut FunctionBuilder<'_>, + offsets: &VMComponentOffsets, + vmctx: ir::Value, + offset: u32, + ) -> ir::Value { + let pointer_type = self.isa.pointer_type(); + // First load the pointer to the libcalls structure which is static + // per-process. + let libcalls_array = builder.ins().load( + pointer_type, + MemFlags::trusted().with_readonly(), + vmctx, + i32::try_from(offsets.libcalls()).unwrap(), + ); + // Next load the function pointer at `offset` and return that. + builder.ins().load( + pointer_type, + MemFlags::trusted().with_readonly(), + libcalls_array, + i32::try_from(offset * u32::from(offsets.ptr.size())).unwrap(), + ) + } + + fn abi_load_params( + &self, + builder: &mut FunctionBuilder<'_>, + ty: &WasmFuncType, + block0: ir::Block, + abi: Abi, + ) -> Vec { + let mut block0_params = builder.func.dfg.block_params(block0).to_vec(); + match abi { + // Wasm and native ABIs pass parameters as normal function + // parameters. + Abi::Wasm | Abi::Native => block0_params, + + // The array ABI passes a pointer/length as the 3rd/4th arguments + // and those are used to load the actual wasm parameters. + Abi::Array => { + let results = self.load_values_from_array( + ty.params(), + builder, + block0_params[2], + block0_params[3], + ); + block0_params.truncate(2); + block0_params.extend(results); + block0_params + } + } + } + + fn abi_store_results( + &self, + builder: &mut FunctionBuilder<'_>, + ty: &WasmFuncType, + block0: ir::Block, + results: &[ir::Value], + abi: Abi, + ) { + match abi { + // Wasm/native ABIs return values as usual. + Abi::Wasm | Abi::Native => { + builder.ins().return_(results); + } + + // The array ABI stores all results in the pointer/length passed + // as arguments to this function, which contractually are required + // to have enough space for the results. + Abi::Array => { + let block0_params = builder.func.dfg.block_params(block0); + self.store_values_to_array( + builder, + ty.returns(), + results, + block0_params[2], + block0_params[3], + ); + builder.ins().return_(&[]); + } + } + } + fn abi_preamble( &self, builder: &mut FunctionBuilder<'_>, @@ -311,6 +668,39 @@ impl ComponentCompiler for Compiler { self.compile_transcoder_for_abi(component, transcoder, types, abi) }) } + + fn compile_resource_new( + &self, + component: &Component, + resource: &ResourceNew, + types: &ComponentTypes, + ) -> Result>> { + self.compile_func_ref(|abi| { + self.compile_resource_new_for_abi(component, resource, types, abi) + }) + } + + fn compile_resource_rep( + &self, + component: &Component, + resource: &ResourceRep, + types: &ComponentTypes, + ) -> Result>> { + self.compile_func_ref(|abi| { + self.compile_resource_rep_for_abi(component, resource, types, abi) + }) + } + + fn compile_resource_drop( + &self, + component: &Component, + resource: &ResourceDrop, + types: &ComponentTypes, + ) -> Result>> { + self.compile_func_ref(|abi| { + self.compile_resource_drop_for_abi(component, resource, types, abi) + }) + } } impl Compiler { @@ -347,20 +737,7 @@ impl Compiler { Transcode::Utf8ToUtf16 => host::utf8_to_utf16(self, func), }; - // Load the host function pointer for this transcode which comes from a - // function pointer within the VMComponentContext's libcall array. - let transcode_libcalls_array = builder.ins().load( - pointer_type, - MemFlags::trusted(), - vmctx, - i32::try_from(offsets.transcode_libcalls()).unwrap(), - ); - let transcode_libcall = builder.ins().load( - pointer_type, - MemFlags::trusted(), - transcode_libcalls_array, - i32::try_from(offset * u32::from(offsets.ptr.size())).unwrap(), - ); + let libcall = self.load_libcall(builder, offsets, vmctx, offset); // Load the base pointers for the from/to linear memories. let from_base = self.load_runtime_memory_base(builder, vmctx, offsets, transcoder.from); @@ -447,7 +824,7 @@ impl Compiler { )); args.push(builder.ins().stack_addr(pointer_type, slot, 0)); } - let call = builder.ins().call_indirect(sig, transcode_libcall, &args); + let call = builder.ins().call_indirect(sig, libcall, &args); let mut results = builder.func.dfg.inst_results(call).to_vec(); if uses_retptr { results.push(builder.ins().load( @@ -530,7 +907,7 @@ mod host { use cranelift_codegen::ir::{self, AbiParam}; use cranelift_codegen::isa::CallConv; - macro_rules! host_transcode { + macro_rules! define { ( $( $( #[$attr:meta] )* @@ -541,10 +918,10 @@ mod host { pub(super) fn $name(compiler: &Compiler, func: &mut ir::Function) -> (ir::SigRef, u32) { let pointer_type = compiler.isa.pointer_type(); let params = vec![ - $( AbiParam::new(host_transcode!(@ty pointer_type $param)) ),* + $( AbiParam::new(define!(@ty pointer_type $param)) ),* ]; let returns = vec![ - $( AbiParam::new(host_transcode!(@ty pointer_type $result)) )? + $( AbiParam::new(define!(@ty pointer_type $result)) )? ]; let sig = func.import_signature(ir::Signature { params, @@ -561,9 +938,13 @@ mod host { (@ty $ptr:ident ptr_u8) => ($ptr); (@ty $ptr:ident ptr_u16) => ($ptr); (@ty $ptr:ident ptr_size) => ($ptr); + (@ty $ptr:ident u32) => (ir::types::I32); + (@ty $ptr:ident u64) => (ir::types::I64); + (@ty $ptr:ident vmctx) => ($ptr); } - wasmtime_environ::foreach_transcoder!(host_transcode); + wasmtime_environ::foreach_transcoder!(define); + wasmtime_environ::foreach_builtin_component_function!(define); mod offsets { macro_rules! offsets { @@ -576,13 +957,32 @@ mod host { offsets!(@declare (0) $($name)*); }; - (@declare ($n:expr)) => (); + (@declare ($n:expr)) => (const LAST_BUILTIN: u32 = $n;); (@declare ($n:expr) $name:ident $($rest:tt)*) => ( - pub static $name: u32 = $n; + pub const $name: u32 = $n; offsets!(@declare ($n + 1) $($rest)*); ); } - wasmtime_environ::foreach_transcoder!(offsets); + wasmtime_environ::foreach_builtin_component_function!(offsets); + + macro_rules! transcode_offsets { + ( + $( + $( #[$attr:meta] )* + $name:ident($($t:tt)*) $( -> $result:ident )?; + )* + ) => { + transcode_offsets!(@declare (0) $($name)*); + }; + + (@declare ($n:expr)) => (); + (@declare ($n:expr) $name:ident $($rest:tt)*) => ( + pub const $name: u32 = LAST_BUILTIN + $n; + transcode_offsets!(@declare ($n + 1) $($rest)*); + ); + } + + wasmtime_environ::foreach_transcoder!(transcode_offsets); } } diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs index b6bf89ff7971..8ea7f6a548ae 100644 --- a/crates/environ/examples/factc.rs +++ b/crates/environ/examples/factc.rs @@ -131,7 +131,7 @@ impl Factc { for i in 0..wasm_types.component_type_count() { let ty = wasm_types.component_type_at(i); let ty = match &wasm_types[ty] { - wasmparser::types::Type::ComponentFunc(ty) => { + wasmparser::types::Type::ComponentFunc(_) => { types.convert_component_func_type(wasm_types, ty)? } _ => continue, diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index d9790bc4f6e3..84fe3559dee3 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -209,7 +209,6 @@ pub trait Compiler: Send + Sync { /// Wasm-to-host transition (e.g. registers used for fast stack walking). fn compile_wasm_to_native_trampoline( &self, - translation: &ModuleTranslation<'_>, wasm_func_ty: &WasmFuncType, ) -> Result, CompileError>; diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index 611f8c07d10e..880ee9bacae8 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -70,3 +70,21 @@ macro_rules! foreach_transcoder { } }; } + +/// Helper macro, like `foreach_transcoder`, to iterate over builtins for +/// components unrelated to transcoding. +#[macro_export] +macro_rules! foreach_builtin_component_function { + ($mac:ident) => { + $mac! { + resource_new32(vmctx: vmctx, resource: u32, rep: u32) -> u32; + resource_rep32(vmctx: vmctx, resource: u32, idx: u32) -> u32; + + // Returns an `Option` where `None` is "no destructor needed" + // and `Some(val)` is "run the destructor on this rep". The option + // is encoded as a 64-bit integer where the low bit is Some/None + // and bits 1-33 are the payload. + resource_drop(vmctx: vmctx, resource: u32, idx: u32) -> u64; + } + }; +} diff --git a/crates/environ/src/component/compiler.rs b/crates/environ/src/component/compiler.rs index d03706851154..b8256845a621 100644 --- a/crates/environ/src/component/compiler.rs +++ b/crates/environ/src/component/compiler.rs @@ -1,4 +1,6 @@ -use crate::component::{Component, ComponentTypes, LowerImport, Transcoder}; +use crate::component::{ + Component, ComponentTypes, LowerImport, ResourceDrop, ResourceNew, ResourceRep, Transcoder, +}; use crate::WasmFuncType; use anyhow::Result; use serde::{Deserialize, Serialize}; @@ -81,4 +83,36 @@ pub trait ComponentCompiler: Send + Sync { transcoder: &Transcoder, types: &ComponentTypes, ) -> Result>>; + + /// Compiles a trampoline to use as the `resource.new` intrinsic in the + /// component model. + /// + /// The generated trampoline will invoke a host-defined libcall that will do + /// all the heavy lifting for this intrinsic, so this is primarily bridging + /// ABIs and inserting a `TypeResourceTableIndex` argument so the host + /// has the context about where this is coming from. + fn compile_resource_new( + &self, + component: &Component, + resource: &ResourceNew, + types: &ComponentTypes, + ) -> Result>>; + + /// Same as `compile_resource_new` except for the `resource.rep` intrinsic. + fn compile_resource_rep( + &self, + component: &Component, + resource: &ResourceRep, + types: &ComponentTypes, + ) -> Result>>; + + /// Similar to `compile_resource_new` but this additionally handles the + /// return value which may involve executing destructors and checking for + /// reentrance traps. + fn compile_resource_drop( + &self, + component: &Component, + resource: &ResourceDrop, + types: &ComponentTypes, + ) -> Result>>; } diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index aa02d6b7d408..48a527992cca 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -28,7 +28,7 @@ //! fused adapters, what arguments make their way to core wasm modules, etc. use crate::component::*; -use crate::{EntityIndex, EntityRef, PrimaryMap, SignatureIndex}; +use crate::{EntityIndex, EntityRef, PrimaryMap, SignatureIndex, WasmType}; use indexmap::IndexMap; use std::collections::HashMap; use std::hash::Hash; @@ -108,6 +108,50 @@ pub struct ComponentDfg { /// as the core wasm index of the export corresponding to the lowered /// version of the adapter. pub adapter_paritionings: PrimaryMap, + + /// Defined resources in this component sorted by index with metadata about + /// each resource. + /// + /// Note that each index here is a unique resource, and that may mean it was + /// the same component instantiated twice for example. + pub resources: PrimaryMap, + + /// Metadata about all imported resources into this component. This records + /// both how many imported resources there are (the size of this map) along + /// with what the corresponding runtime import is. + pub imported_resources: PrimaryMap, + + /// The total number of resource tables that will be used by this component, + /// currently the number of unique `TypeResourceTableIndex` allocations for + /// this component. + pub num_resource_tables: usize, + + /// An ordered list of side effects induced by instantiating this component. + /// + /// Currently all side effects are either instantiating core wasm modules or + /// declaring a resource. These side effects affect the dataflow processing + /// of this component by idnicating what order operations should be + /// performed during instantiation. + pub side_effects: Vec, +} + +/// Possible side effects that are possible with instantiating this component. +pub enum SideEffect { + /// A core wasm instance was created. + /// + /// Instantiation is side-effectful due to the presence of constructs such + /// as traps and the core wasm `start` function which may call component + /// imports. Instantiation order from the original component must be done in + /// the same order. + Instance(InstanceId), + + /// A resource was declared in this component. + /// + /// This is a bit less side-effectful than instantiation but this serves as + /// the order in which resources are initialized in a component with their + /// destructors. Destructors are loaded from core wasm instances (or + /// lowerings) which are produced by prior side-effectful operations. + Resource(DefinedResourceIndex), } macro_rules! id { @@ -165,6 +209,10 @@ pub enum CoreDef { InstanceFlags(RuntimeComponentInstanceIndex), Transcoder(TranscoderId), + ResourceNew(TypeResourceTableIndex, SignatureIndex), + ResourceRep(TypeResourceTableIndex, SignatureIndex), + ResourceDrop(TypeResourceTableIndex, SignatureIndex), + /// This is a special variant not present in `info::CoreDef` which /// represents that this definition refers to a fused adapter function. This /// adapter is fully processed after the initial translation and @@ -213,6 +261,7 @@ pub struct LowerImport { pub import: RuntimeImportIndex, pub canonical_abi: SignatureIndex, pub options: CanonicalOptions, + pub lower_ty: TypeFuncIndex, } /// Same as `info::CanonicalOptions` @@ -238,6 +287,14 @@ pub struct Transcoder { pub signature: SignatureIndex, } +/// Same as `info::Resource` +#[allow(missing_docs)] +pub struct Resource { + pub rep: WasmType, + pub dtor: Option, + pub instance: RuntimeComponentInstanceIndex, +} + /// A helper structure to "intern" and deduplicate values of type `V` with an /// identifying key `K`. /// @@ -310,17 +367,20 @@ impl ComponentDfg { runtime_always_trap: Default::default(), runtime_lowerings: Default::default(), runtime_transcoders: Default::default(), + runtime_resource_new: Default::default(), + runtime_resource_rep: Default::default(), + runtime_resource_drop: Default::default(), }; - // First the instances are all processed for instantiation. This will, - // recursively, handle any arguments necessary for each instance such as - // instantiation of adapter modules. - for (id, instance) in linearize.dfg.instances.key_map.iter() { - linearize.instantiate(id, instance); + // Handle all side effects of this component in the order that they're + // defined. This will, for example, process all instantiations necessary + // of core wasm modules. + for item in linearize.dfg.side_effects.iter() { + linearize.side_effect(item); } - // Second the exports of the instance are handled which will likely end - // up creating some lowered imports, perhaps some saved modules, etc. + // Next the exports of the instance are handled which will likely end up + // creating some lowered imports, perhaps some saved modules, etc. let exports = self .exports .iter() @@ -342,12 +402,25 @@ impl ComponentDfg { num_always_trap: linearize.runtime_always_trap.len() as u32, num_lowerings: linearize.runtime_lowerings.len() as u32, num_transcoders: linearize.runtime_transcoders.len() as u32, + num_resource_new: linearize.runtime_resource_new.len() as u32, + num_resource_rep: linearize.runtime_resource_rep.len() as u32, + num_resource_drop: linearize.runtime_resource_drop.len() as u32, imports: self.imports, import_types: self.import_types, num_runtime_component_instances: self.num_runtime_component_instances, + num_resource_tables: self.num_resource_tables, + num_resources: (self.resources.len() + self.imported_resources.len()) as u32, + imported_resources: self.imported_resources, + defined_resource_instances: self.resources.iter().map(|(_, r)| r.instance).collect(), } } + + /// Converts the provided defined index into a normal index, adding in the + /// number of imported resources. + pub fn resource_index(&self, defined: DefinedResourceIndex) -> ResourceIndex { + ResourceIndex::from_u32(defined.as_u32() + (self.imported_resources.len() as u32)) + } } struct LinearizeDfg<'a> { @@ -360,6 +433,9 @@ struct LinearizeDfg<'a> { runtime_always_trap: HashMap, runtime_lowerings: HashMap, runtime_transcoders: HashMap, + runtime_resource_new: HashMap, + runtime_resource_rep: HashMap, + runtime_resource_drop: HashMap, } #[derive(Copy, Clone, Hash, Eq, PartialEq)] @@ -369,6 +445,17 @@ enum RuntimeInstance { } impl LinearizeDfg<'_> { + fn side_effect(&mut self, effect: &SideEffect) { + match effect { + SideEffect::Instance(i) => { + self.instantiate(*i, &self.dfg.instances[*i]); + } + SideEffect::Resource(i) => { + self.resource(*i, &self.dfg.resources[*i]); + } + } + } + fn instantiate(&mut self, instance: InstanceId, args: &Instance) { log::trace!("creating instance {instance:?}"); let instantiation = match args { @@ -398,6 +485,17 @@ impl LinearizeDfg<'_> { assert!(prev.is_none()); } + fn resource(&mut self, index: DefinedResourceIndex, resource: &Resource) { + let dtor = resource.dtor.as_ref().map(|dtor| self.core_def(dtor)); + self.initializers + .push(GlobalInitializer::Resource(info::Resource { + dtor, + index, + rep: resource.rep, + instance: resource.instance, + })); + } + fn export(&mut self, export: &Export) -> info::Export { match export { Export::LiftedFunction { ty, func, options } => { @@ -468,6 +566,11 @@ impl LinearizeDfg<'_> { CoreDef::InstanceFlags(i) => info::CoreDef::InstanceFlags(*i), CoreDef::Adapter(id) => info::CoreDef::Export(self.adapter(*id)), CoreDef::Transcoder(id) => info::CoreDef::Transcoder(self.runtime_transcoder(*id)), + CoreDef::ResourceNew(id, ty) => info::CoreDef::ResourceNew(self.resource_new(*id, *ty)), + CoreDef::ResourceRep(id, ty) => info::CoreDef::ResourceRep(self.resource_rep(*id, *ty)), + CoreDef::ResourceDrop(id, ty) => { + info::CoreDef::ResourceDrop(self.resource_drop(*id, *ty)) + } } } @@ -492,14 +595,15 @@ impl LinearizeDfg<'_> { |me, id| { let info = &me.dfg.lowerings[id]; let options = me.options(&info.options); - (info.import, info.canonical_abi, options) + (info.import, info.canonical_abi, options, info.lower_ty) }, - |index, (import, canonical_abi, options)| { + |index, (import, canonical_abi, options, lower_ty)| { GlobalInitializer::LowerImport(info::LowerImport { index, import, canonical_abi, options, + lower_ty, }) }, ) @@ -534,6 +638,63 @@ impl LinearizeDfg<'_> { ) } + fn resource_new( + &mut self, + id: TypeResourceTableIndex, + signature: SignatureIndex, + ) -> RuntimeResourceNewIndex { + self.intern( + id, + |me| &mut me.runtime_resource_new, + |_me, id| id, + |index, resource| { + GlobalInitializer::ResourceNew(info::ResourceNew { + index, + resource, + signature, + }) + }, + ) + } + + fn resource_rep( + &mut self, + id: TypeResourceTableIndex, + signature: SignatureIndex, + ) -> RuntimeResourceRepIndex { + self.intern( + id, + |me| &mut me.runtime_resource_rep, + |_me, id| id, + |index, resource| { + GlobalInitializer::ResourceRep(info::ResourceRep { + index, + resource, + signature, + }) + }, + ) + } + + fn resource_drop( + &mut self, + id: TypeResourceTableIndex, + signature: SignatureIndex, + ) -> RuntimeResourceDropIndex { + self.intern( + id, + |me| &mut me.runtime_resource_drop, + |_me, ty| ty, + |index, resource| { + GlobalInitializer::ResourceDrop(info::ResourceDrop { + index, + resource, + signature, + }) + }, + ) + } + fn core_export(&mut self, export: &CoreExport) -> info::CoreExport where T: Clone, diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 74e5189422ef..1179491a0a6f 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -47,7 +47,7 @@ // requirements of embeddings change over time. use crate::component::*; -use crate::{EntityIndex, PrimaryMap, SignatureIndex}; +use crate::{EntityIndex, PrimaryMap, SignatureIndex, WasmType}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -147,21 +147,54 @@ pub struct Component { /// The number of host transcoder functions needed for strings in adapter /// modules. pub num_transcoders: u32, + + /// Number of `ResourceNew` initializers in the global initializers list. + pub num_resource_new: u32, + + /// Number of `ResourceRep` initializers in the global initializers list. + pub num_resource_rep: u32, + + /// Number of `ResourceDrop` initializers in the global initializers list. + pub num_resource_drop: u32, + + /// Maximal number of tables that required at runtime for resource-related + /// information in this component. + pub num_resource_tables: usize, + + /// Total number of resources both imported and defined within this + /// component. + pub num_resources: u32, + + /// Metadata about imported resources and where they are within the runtime + /// imports array. + /// + /// This map is only as large as the number of imported resources. + pub imported_resources: PrimaryMap, + + /// Metadata about which component instances defined each resource within + /// this component. + /// + /// This is used to determine which set of instance flags are inspected when + /// testing reentrance. + pub defined_resource_instances: PrimaryMap, } impl Component { - /// Returns the type of the specified import - pub fn type_of_import(&self, import: RuntimeImportIndex, types: &ComponentTypes) -> TypeDef { - let (index, path) = &self.imports[import]; - let (_, mut ty) = self.import_types[*index]; - for entry in path { - let instance_ty = match ty { - TypeDef::ComponentInstance(ty) => ty, - _ => unreachable!(), - }; - ty = types[instance_ty].exports[entry]; - } - ty + /// Attempts to convert a resource index into a defined index. + /// + /// Returns `None` if `idx` is for an imported resource in this component or + /// `Some` if it's a locally defined resource. + pub fn defined_resource_index(&self, idx: ResourceIndex) -> Option { + let idx = idx + .as_u32() + .checked_sub(self.imported_resources.len() as u32)?; + Some(DefinedResourceIndex::from_u32(idx)) + } + + /// Converts a defined resource index to a component-local resource index + /// which includes all imports. + pub fn resource_index(&self, idx: DefinedResourceIndex) -> ResourceIndex { + ResourceIndex::from_u32(self.imported_resources.len() as u32 + idx.as_u32()) } } @@ -221,6 +254,23 @@ pub enum GlobalInitializer { /// needs to be initialized for a transcoder function and this will later be /// used to instantiate an adapter module. Transcoder(Transcoder), + + /// Declares a new defined resource within this component. + /// + /// Contains information about the destructor, for example. + Resource(Resource), + + /// Declares a new `resource.new` intrinsic should be initialized. + /// + /// This will initialize a `VMFuncRef` within the `VMComponentContext` for + /// the described resource. + ResourceNew(ResourceNew), + + /// Same as `ResourceNew`, but for `resource.rep` intrinsics. + ResourceRep(ResourceRep), + + /// Same as `ResourceNew`, but for `resource.drop` intrinsics. + ResourceDrop(ResourceDrop), } /// Metadata for extraction of a memory of what's being extracted and where it's @@ -288,6 +338,10 @@ pub struct LowerImport { /// It's guaranteed that this `RuntimeImportIndex` points to a function. pub import: RuntimeImportIndex, + /// The type of the function that is being lowered, as perceived by the + /// component doing the lowering. + pub lower_ty: TypeFuncIndex, + /// The core wasm signature of the function that's being created. pub canonical_abi: SignatureIndex, @@ -347,6 +401,15 @@ pub enum CoreDef { /// This refers to a cranelift-generated trampoline which calls to a /// host-defined transcoding function. Transcoder(RuntimeTranscoderIndex), + + /// This refers to a `resource.new` intrinsic described by the index + /// provided. These indices are created through `GlobalInitializer` + /// entries. + ResourceNew(RuntimeResourceNewIndex), + /// Same as `ResourceNew`, but for the `resource.rep` intrinsic + ResourceRep(RuntimeResourceRepIndex), + /// Same as `ResourceNew`, but for the `resource.drop` intrinsic + ResourceDrop(RuntimeResourceDropIndex), } impl From> for CoreDef @@ -516,3 +579,76 @@ impl Transcoder { } pub use crate::fact::{FixedEncoding, Transcode}; + +/// Description of a new resource declared in a `GlobalInitializer::Resource` +/// variant. +/// +/// This will have the effect of initializing runtime state for this resource, +/// namely the destructor is fetched and stored. +#[derive(Debug, Serialize, Deserialize)] +pub struct Resource { + /// The local index of the resource being defined. + pub index: DefinedResourceIndex, + /// Core wasm representation of this resource. + pub rep: WasmType, + /// Optionally-specified destructor and where it comes from. + pub dtor: Option, + /// Which component instance this resource logically belongs to. + pub instance: RuntimeComponentInstanceIndex, +} + +/// Description of a `resource.new` intrinsic used to declare and initialize a +/// new `VMFuncRef` which generates the core wasm function corresponding to +/// `resource.new`. +#[derive(Debug, Serialize, Deserialize)] +pub struct ResourceNew { + /// The index of the intrinsic being created. + pub index: RuntimeResourceNewIndex, + /// The resource table that this intrinsic will be modifying. + pub resource: TypeResourceTableIndex, + /// The core wasm signature of the intrinsic, always `(func (param i32) + /// (result i32))`. + pub signature: SignatureIndex, +} + +impl ResourceNew { + /// Returns the compiled artifact symbol name for this intrinsic. + pub fn symbol_name(&self) -> String { + let resource = self.resource.as_u32(); + format!("wasm_component_resource_new{resource}") + } +} + +/// Same as `ResourceNew`, but for the `resource.rep` intrinsic. +#[derive(Debug, Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct ResourceRep { + pub index: RuntimeResourceRepIndex, + pub resource: TypeResourceTableIndex, + pub signature: SignatureIndex, +} + +impl ResourceRep { + /// Returns the compiled artifact symbol name for this intrinsic. + pub fn symbol_name(&self) -> String { + let resource = self.resource.as_u32(); + format!("wasm_component_resource_rep{resource}") + } +} + +/// Same as `ResourceNew`, but for the `resource.drop` intrinsic. +#[derive(Debug, Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct ResourceDrop { + pub index: RuntimeResourceDropIndex, + pub resource: TypeResourceTableIndex, + pub signature: SignatureIndex, +} + +impl ResourceDrop { + /// Returns the compiled artifact symbol name for this intrinsic. + pub fn symbol_name(&self) -> String { + let resource = self.resource.as_u32(); + format!("wasm_component_resource_drop{resource}") + } +} diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 5133a2064b8a..09c8ea12ff59 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -1,13 +1,14 @@ use crate::component::*; use crate::ScopeVec; use crate::{ - EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, SignatureIndex, Tunables, - TypeConvert, + EntityIndex, ModuleEnvironment, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, + SignatureIndex, Tunables, TypeConvert, WasmHeapType, WasmType, }; use anyhow::{bail, Result}; use indexmap::IndexMap; use std::collections::HashMap; use std::mem; +use wasmparser::types::{ComponentEntityType, TypeId, Types}; use wasmparser::{Chunk, ComponentExternName, Encoding, Parser, Payload, Validator}; mod adapt; @@ -43,7 +44,7 @@ pub struct Translator<'a, 'data> { /// /// This builder is also used for all core wasm modules found to intern /// signatures across all modules. - types: &'a mut ComponentTypesBuilder, + types: PreInliningComponentTypes<'a>, /// The compiler configuration provided by the embedder. tunables: &'a Tunables, @@ -148,24 +149,38 @@ struct Translation<'data> { /// index into an index space of what's being exported. exports: IndexMap<&'data str, ComponentItem>, - /// The next core function index that's going to be defined, used to lookup - /// type information when lowering. - core_func_index: u32, + /// Type information produced by `wasmparser` for this component. + /// + /// This type information is available after the translation of the entire + /// component has finished, e.g. for the `inline` pass, but beforehand this + /// is set to `None`. + types: Option, } +// NB: the type information contained in `LocalInitializer` should always point +// to `wasmparser`'s type information, not Wasmtime's. Component types cannot be +// fully determined due to resources until instantiations are known which is +// tracked during the inlining phase. This means that all type information below +// is straight from `wasmparser`'s passes. #[allow(missing_docs)] enum LocalInitializer<'data> { // imports - Import(ComponentExternName<'data>, TypeDef), + Import(ComponentExternName<'data>, ComponentEntityType), // canonical function sections Lower { func: ComponentFuncIndex, - lower_ty: TypeFuncIndex, + lower_ty: TypeId, canonical_abi: SignatureIndex, options: LocalCanonicalOptions, }, - Lift(TypeFuncIndex, FuncIndex, LocalCanonicalOptions), + Lift(TypeId, FuncIndex, LocalCanonicalOptions), + + // resources + Resource(TypeId, WasmType, Option), + ResourceNew(TypeId, SignatureIndex), + ResourceRep(TypeId, SignatureIndex), + ResourceDrop(TypeId, SignatureIndex), // core wasm modules ModuleStatic(StaticModuleIndex), @@ -178,7 +193,7 @@ enum LocalInitializer<'data> { ComponentStatic(StaticComponentIndex, ClosedOverVars), // component instances - ComponentInstantiate(ComponentIndex, HashMap<&'data str, ComponentItem>), + ComponentInstantiate(ComponentIndex, HashMap<&'data str, ComponentItem>, TypeId), ComponentSynthetic(HashMap<&'data str, ComponentItem>), // alias section @@ -250,7 +265,7 @@ impl<'a, 'data> Translator<'a, 'data> { result: Translation::default(), tunables, validator, - types, + types: PreInliningComponentTypes::new(types), parser: Parser::new(0), lexical_scopes: Vec::new(), static_components: Default::default(), @@ -324,7 +339,7 @@ impl<'a, 'data> Translator<'a, 'data> { // Wasmtime to process at runtime as well (e.g. no string lookups as // most everything is done through indices instead). let mut component = inline::run( - &self.types, + self.types.types_mut_for_inlining(), &self.result, &self.static_modules, &self.static_components, @@ -355,7 +370,8 @@ impl<'a, 'data> Translator<'a, 'data> { } Payload::End(offset) => { - self.validator.end(offset)?; + assert!(self.result.types.is_none()); + self.result.types = Some(self.validator.end(offset)?); // Exit the current lexical scope. If there is no parent (no // frame currently on the stack) then translation is finished. @@ -386,7 +402,34 @@ impl<'a, 'data> Translator<'a, 'data> { // in `Version` and `End` since multiple type sections can appear // within a component. Payload::ComponentTypeSection(s) => { + let mut component_type_index = + self.validator.types(0).unwrap().component_type_count(); self.validator.component_type_section(&s)?; + + // Look for resource types and if a local resource is defined + // then an initializer is added to define that resource type and + // reference its destructor. + let types = self.validator.types(0).unwrap(); + for ty in s { + match ty? { + wasmparser::ComponentType::Resource { rep, dtor } => { + let rep = self.types.convert_valtype(rep); + let id = types.component_type_at(component_type_index); + let dtor = dtor.map(FuncIndex::from_u32); + self.result + .initializers + .push(LocalInitializer::Resource(id, rep, dtor)); + } + + // no extra processing needed + wasmparser::ComponentType::Defined(_) + | wasmparser::ComponentType::Func(_) + | wasmparser::ComponentType::Instance(_) + | wasmparser::ComponentType::Component(_) => {} + } + + component_type_index += 1; + } } Payload::CoreTypeSection(s) => { self.validator.core_type_section(&s)?; @@ -403,7 +446,6 @@ impl<'a, 'data> Translator<'a, 'data> { let ty = types .component_entity_type_of_import(import.name.as_str()) .unwrap(); - let ty = self.types.convert_component_entity_type(types, ty)?; self.result .initializers .push(LocalInitializer::Import(import.name, ty)); @@ -413,59 +455,58 @@ impl<'a, 'data> Translator<'a, 'data> { // Entries in the canonical section will get initializers recorded // with the listed options for lifting/lowering. Payload::ComponentCanonicalSection(s) => { + let mut core_func_index = self.validator.types(0).unwrap().function_count(); self.validator.component_canonical_section(&s)?; - let types = self.validator.types(0).unwrap(); for func in s { - match func? { + let types = self.validator.types(0).unwrap(); + let init = match func? { wasmparser::CanonicalFunction::Lift { type_index, core_func_index, options, } => { - let ty = - types[types.component_type_at(type_index)].unwrap_component_func(); - let ty = self.types.convert_component_func_type(types, ty)?; + let ty = types.component_type_at(type_index); let func = FuncIndex::from_u32(core_func_index); let options = self.canonical_options(&options); - self.result - .initializers - .push(LocalInitializer::Lift(ty, func, options)); + LocalInitializer::Lift(ty, func, options) } wasmparser::CanonicalFunction::Lower { func_index, options, } => { let lower_ty = types.component_function_at(func_index); - let lower_ty = types[lower_ty].unwrap_component_func(); - let lower_ty = - self.types.convert_component_func_type(types, lower_ty)?; - let func = ComponentFuncIndex::from_u32(func_index); let options = self.canonical_options(&options); + let canonical_abi = self.core_func_signature(core_func_index); - let canonical_abi = types.function_at(self.result.core_func_index); - let canonical_abi = types[canonical_abi].unwrap_func(); - let canonical_abi = self.types.convert_func_type(canonical_abi); - let canonical_abi = self - .types - .module_types_builder() - .wasm_func_type(canonical_abi); - - self.result.initializers.push(LocalInitializer::Lower { + core_func_index += 1; + LocalInitializer::Lower { func, options, canonical_abi, lower_ty, - }); - self.result.core_func_index += 1; + } } - - wasmparser::CanonicalFunction::ResourceNew { .. } - | wasmparser::CanonicalFunction::ResourceDrop { .. } - | wasmparser::CanonicalFunction::ResourceRep { .. } => { - unimplemented!("resource types") + wasmparser::CanonicalFunction::ResourceNew { resource } => { + let resource = types.component_type_at(resource); + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceNew(resource, ty) } - } + wasmparser::CanonicalFunction::ResourceDrop { resource } => { + let resource = types.component_type_at(resource); + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceDrop(resource, ty) + } + wasmparser::CanonicalFunction::ResourceRep { resource } => { + let resource = types.component_type_at(resource); + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceRep(resource, ty) + } + }; + self.result.initializers.push(init); } } @@ -529,6 +570,7 @@ impl<'a, 'data> Translator<'a, 'data> { } } Payload::ComponentInstanceSection(s) => { + let mut index = self.validator.types(0).unwrap().component_instance_count(); self.validator.component_instance_section(&s)?; for instance in s { let init = match instance? { @@ -536,14 +578,17 @@ impl<'a, 'data> Translator<'a, 'data> { component_index, args, } => { + let types = self.validator.types(0).unwrap(); + let ty = types.component_instance_at(index); let index = ComponentIndex::from_u32(component_index); - self.instantiate_component(index, &args)? + self.instantiate_component(index, &args, ty)? } wasmparser::ComponentInstance::FromExports(exports) => { self.instantiate_component_from_exports(&exports)? } }; self.result.initializers.push(init); + index += 1; } } @@ -677,6 +722,7 @@ impl<'a, 'data> Translator<'a, 'data> { &mut self, component: ComponentIndex, raw_args: &[wasmparser::ComponentInstantiationArg<'data>], + ty: TypeId, ) -> Result> { let mut args = HashMap::with_capacity(raw_args.len()); for arg in raw_args { @@ -684,7 +730,7 @@ impl<'a, 'data> Translator<'a, 'data> { args.insert(arg.name, idx); } - Ok(LocalInitializer::ComponentInstantiate(component, args)) + Ok(LocalInitializer::ComponentInstantiate(component, args, ty)) } /// Creates a synthetic module from the list of items currently in the @@ -730,7 +776,6 @@ impl<'a, 'data> Translator<'a, 'data> { wasmparser::ComponentExternalKind::Type => { let types = self.validator.types(0).unwrap(); let ty = types.component_type_at(index); - let ty = self.types.convert_type(types, ty)?; ComponentItem::Type(ty) } }) @@ -743,10 +788,7 @@ impl<'a, 'data> Translator<'a, 'data> { name: &'data str, ) -> LocalInitializer<'data> { match kind { - wasmparser::ExternalKind::Func => { - self.result.core_func_index += 1; - LocalInitializer::AliasExportFunc(instance, name) - } + wasmparser::ExternalKind::Func => LocalInitializer::AliasExportFunc(instance, name), wasmparser::ExternalKind::Memory => LocalInitializer::AliasExportMemory(instance, name), wasmparser::ExternalKind::Table => LocalInitializer::AliasExportTable(instance, name), wasmparser::ExternalKind::Global => LocalInitializer::AliasExportGlobal(instance, name), @@ -763,8 +805,8 @@ impl<'a, 'data> Translator<'a, 'data> { index: u32, ) { match kind { - wasmparser::ComponentOuterAliasKind::CoreType => {} - wasmparser::ComponentOuterAliasKind::Type => {} + wasmparser::ComponentOuterAliasKind::CoreType + | wasmparser::ComponentOuterAliasKind::Type => {} // For more information about the implementation of outer aliases // see the documentation of `LexicalScope`. Otherwise though the @@ -838,4 +880,64 @@ impl<'a, 'data> Translator<'a, 'data> { } return ret; } + + fn core_func_signature(&mut self, idx: u32) -> SignatureIndex { + let types = self.validator.types(0).unwrap(); + let id = types.function_at(idx); + let ty = types[id].unwrap_func(); + let ty = self.types.convert_func_type(ty); + self.types.module_types_builder().wasm_func_type(ty) + } +} + +impl Translation<'_> { + fn types_ref(&self) -> wasmparser::types::TypesRef<'_> { + self.types.as_ref().unwrap().as_ref() + } +} + +/// A small helper module which wraps a `ComponentTypesBuilder` and attempts +/// to disallow access to mutable access to the builder before the inlining +/// pass. +/// +/// Type information in this translation pass must be preserved at the +/// wasmparser layer of abstraction rather than being lowered into Wasmtime's +/// own type system. Only during inlining are types fully assigned because +/// that's when resource types become available as it's known which instance +/// defines which resource, or more concretely the same component instantiated +/// twice will produce two unique resource types unlike one as seen by +/// wasmparser within the component. +mod pre_inlining { + use super::*; + + pub struct PreInliningComponentTypes<'a> { + types: &'a mut ComponentTypesBuilder, + } + + impl<'a> PreInliningComponentTypes<'a> { + pub fn new(types: &'a mut ComponentTypesBuilder) -> Self { + Self { types } + } + + pub fn module_types_builder(&mut self) -> &mut ModuleTypesBuilder { + self.types.module_types_builder() + } + + pub fn types(&self) -> &ComponentTypesBuilder { + self.types + } + + // NB: this should in theory only be used for the `inline` phase of + // translation. + pub fn types_mut_for_inlining(&mut self) -> &mut ComponentTypesBuilder { + self.types + } + } + + impl TypeConvert for PreInliningComponentTypes<'_> { + fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType { + self.types.lookup_heap_type(index) + } + } } +use pre_inlining::PreInliningComponentTypes; diff --git a/crates/environ/src/component/translate/adapt.rs b/crates/environ/src/component/translate/adapt.rs index e8eefdc1fe56..b695e58c30dc 100644 --- a/crates/environ/src/component/translate/adapt.rs +++ b/crates/environ/src/component/translate/adapt.rs @@ -184,7 +184,8 @@ impl<'data> Translator<'_, 'data> { // the module using standard core wasm translation, and then fills out // the dfg metadata for each adapter. for (module_id, adapter_module) in state.adapter_modules.iter() { - let mut module = fact::Module::new(self.types, self.tunables.debug_adapter_modules); + let mut module = + fact::Module::new(self.types.types(), self.tunables.debug_adapter_modules); let mut names = Vec::with_capacity(adapter_module.adapters.len()); for adapter in adapter_module.adapters.iter() { let name = format!("adapter{}", adapter.as_u32()); @@ -380,7 +381,10 @@ impl PartitionAdapterModules { // These items can't transitively depend on an adapter dfg::CoreDef::Lowered(_) | dfg::CoreDef::AlwaysTrap(_) - | dfg::CoreDef::InstanceFlags(_) => {} + | dfg::CoreDef::InstanceFlags(_) + | dfg::CoreDef::ResourceNew(..) + | dfg::CoreDef::ResourceDrop(..) + | dfg::CoreDef::ResourceRep(..) => {} // should not be in the dfg yet dfg::CoreDef::Transcoder(_) => unreachable!(), diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 06348c975677..c0b68b345537 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -49,15 +49,15 @@ use crate::component::translate::adapt::{Adapter, AdapterOptions}; use crate::component::translate::*; use crate::{EntityType, PrimaryMap}; use indexmap::IndexMap; +use std::borrow::Cow; pub(super) fn run( - types: &ComponentTypesBuilder, + types: &mut ComponentTypesBuilder, result: &Translation<'_>, nested_modules: &PrimaryMap>, nested_components: &PrimaryMap>, ) -> Result { let mut inliner = Inliner { - types, nested_modules, nested_components, result: Default::default(), @@ -65,6 +65,8 @@ pub(super) fn run( runtime_instances: PrimaryMap::default(), }; + let index = RuntimeComponentInstanceIndex::from_u32(0); + // The initial arguments to the root component are all host imports. This // means that they're all using the `ComponentItemDef::Host` variant. Here // an `ImportIndex` is allocated for each item and then the argument is @@ -73,15 +75,45 @@ pub(super) fn run( // Note that this is represents the abstract state of a host import of an // item since we don't know the precise structure of the host import. let mut args = HashMap::with_capacity(result.exports.len()); + let mut path = Vec::new(); + types.resources_mut().set_current_instance(index); + let types_ref = result.types_ref(); for init in result.initializers.iter() { let (name, ty) = match *init { - // Imports of types (which are currently always equality-bounded) - // are not required to be specified by the host since it's just for - // type information within the component. - LocalInitializer::Import(_, TypeDef::Interface(_)) => continue, LocalInitializer::Import(name, ty) => (name, ty), _ => continue, }; + + // Before `convert_component_entity_type` below all resource types + // introduced by this import need to be registered and have indexes + // assigned to them. Any fresh new resource type referred to by imports + // is a brand new introduction of a resource which needs to have a type + // allocated to it, so new runtime imports are injected for each + // resource along with updating the `imported_resources` map. + let index = inliner.result.import_types.next_key(); + types.resources_mut().register_component_entity_type( + &types_ref, + ty, + &mut path, + &mut |path| { + let index = inliner.runtime_import(&ImportPath { + index, + path: path.iter().copied().map(Into::into).collect(), + }); + inliner.result.imported_resources.push(index) + }, + ); + + // With resources all taken care of it's now possible to convert this + // into Wasmtime's type system. + let ty = types.convert_component_entity_type(types_ref, ty)?; + + // Imports of types that aren't resources are not required to be + // specified by the host since it's just for type information within + // the component. + if let TypeDef::Interface(_) = ty { + continue; + } let index = inliner .result .import_types @@ -94,30 +126,24 @@ pub(super) fn run( // initial frame. When the inliner finishes it will return the exports of // the root frame which are then used for recording the exports of the // component. - let index = RuntimeComponentInstanceIndex::from_u32(0); inliner.result.num_runtime_component_instances += 1; - let mut frames = vec![InlinerFrame::new( - index, - result, - ComponentClosure::default(), - args, - )]; - let exports = inliner.run(&mut frames)?; + let frame = InlinerFrame::new(index, result, ComponentClosure::default(), args, None); + let resources_snapshot = types.resources_mut().clone(); + let mut frames = vec![(frame, resources_snapshot)]; + let exports = inliner.run(types, &mut frames)?; assert!(frames.is_empty()); let mut export_map = Default::default(); for (name, def) in exports { - inliner.record_export(name, def, &mut export_map)?; + inliner.record_export(name, def, types, &mut export_map)?; } inliner.result.exports = export_map; + inliner.result.num_resource_tables = types.num_resource_tables(); Ok(inliner.result) } struct Inliner<'a> { - /// Global type information for the entire component. - types: &'a ComponentTypesBuilder, - /// The list of static modules that were found during initial translation of /// the component. /// @@ -186,6 +212,15 @@ struct InlinerFrame<'a> { module_instances: PrimaryMap>, component_instances: PrimaryMap>, components: PrimaryMap>, + + /// The type of instance produced by completing the instantiation of this + /// frame. + /// + /// This is a wasmparser-relative piece of type information which is used to + /// register resource types after instantiation has completed. + /// + /// This is `Some` for all subcomponents and `None` for the root component. + instance_ty: Option, } /// "Closure state" for a component which is resolved from the `ClosedOverVars` @@ -218,7 +253,7 @@ struct ComponentClosure<'a> { #[derive(Clone, PartialEq, Hash, Eq)] struct ImportPath<'a> { index: ImportIndex, - path: Vec<&'a str>, + path: Vec>, } /// Representation of all items which can be defined within a component. @@ -307,9 +342,24 @@ struct ComponentDef<'a> { } impl<'a> Inliner<'a> { + /// Symbolically instantiates a component using the type information and + /// `frames` provided. + /// + /// The `types` provided is the type information for the entire component + /// translation process. This is a distinct output artifact separate from + /// the component metadata. + /// + /// The `frames` argument is storage to handle a "call stack" of components + /// instantiating one another. The youngest frame (last element) of the + /// frames list is a component that's currently having its initializers + /// processed. The second element of each frame is a snapshot of the + /// resource-related information just before the frame was translated. For + /// more information on this snapshotting see the documentation on + /// `ResourcesBuilder`. fn run( &mut self, - frames: &mut Vec>, + types: &mut ComponentTypesBuilder, + frames: &mut Vec<(InlinerFrame<'a>, ResourcesBuilder)>, ) -> Result>> { // This loop represents the execution of the instantiation of a // component. This is an iterative process which is finished once all @@ -317,13 +367,16 @@ impl<'a> Inliner<'a> { // loop which drives the top-most iterator of the `frames` stack // provided as an argument to this function. loop { - let frame = frames.last_mut().unwrap(); + let (frame, _) = frames.last_mut().unwrap(); + types.resources_mut().set_current_instance(frame.instance); match frame.initializers.next() { // Process the initializer and if it started the instantiation // of another component then we push that frame on the stack to // continue onwards. - Some(init) => match self.initializer(frame, init)? { - Some(new_frame) => frames.push(new_frame), + Some(init) => match self.initializer(frame, types, init)? { + Some(new_frame) => { + frames.push((new_frame, types.resources_mut().clone())); + } None => {} }, @@ -338,14 +391,18 @@ impl<'a> Inliner<'a> { .translation .exports .iter() - .map(|(name, item)| (*name, frame.item(*item))) - .collect(); - frames.pop(); + .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) + .collect::>()?; + let instance_ty = frame.instance_ty; + let (_, snapshot) = frames.pop().unwrap(); + *types.resources_mut() = snapshot; match frames.last_mut() { - Some(parent) => { - parent - .component_instances - .push(ComponentInstanceDef::Items(exports)); + Some((parent, _)) => { + parent.finish_instantiate( + ComponentInstanceDef::Items(exports), + instance_ty.unwrap(), + types, + ); } None => break Ok(exports), } @@ -357,18 +414,12 @@ impl<'a> Inliner<'a> { fn initializer( &mut self, frame: &mut InlinerFrame<'a>, + types: &mut ComponentTypesBuilder, initializer: &'a LocalInitializer, ) -> Result>> { use LocalInitializer::*; match initializer { - // Importing a type into a component is ignored. All type imports - // are equality-bound right now which means that it's purely - // informational name about the type such as a name to assign it. - // Otherwise type imports have no effect on runtime or such, so skip - // them. - Import(_, TypeDef::Interface(_)) => {} - // When a component imports an item the actual definition of the // item is looked up here (not at runtime) via its name. The // arguments provided in our `InlinerFrame` describe how each @@ -379,21 +430,60 @@ impl<'a> Inliner<'a> { // but for sub-components this will do resolution to connect what // was provided as an import at the instantiation-site to what was // needed during the component's instantiation. - Import(name, _ty) => match &frame.args[name.as_str()] { - ComponentItemDef::Module(i) => { - frame.modules.push(i.clone()); - } - ComponentItemDef::Component(i) => { - frame.components.push(i.clone()); - } - ComponentItemDef::Instance(i) => { - frame.component_instances.push(i.clone()); - } - ComponentItemDef::Func(i) => { - frame.component_funcs.push(i.clone()); - } - ComponentItemDef::Type(_ty) => unreachable!(), - }, + Import(name, ty) => { + let arg = match frame.args.get(name.as_str()) { + Some(arg) => arg, + + // Not all arguments need to be provided for instantiation, + // namely the root component in Wasmtime doesn't require + // structural type imports to be satisfied. These type + // imports are relevant for bindings generators and such but + // as a runtime there's not really a definition to fit in. + // + // If no argument was provided for `name` then it's asserted + // that this is a type import and additionally it's not a + // resource type import (which indeed must be provided). If + // all that passes then this initializer is effectively + // skipped. + None => { + match ty { + ComponentEntityType::Type { created, .. } => { + match frame.translation.types_ref()[*created] { + wasmparser::types::Type::Resource(_) => unreachable!(), + _ => {} + } + } + _ => unreachable!(), + } + return Ok(None); + } + }; + + // Next resource types need to be handled. For example if a + // resource is imported into this component then it needs to be + // assigned a unique table to provide the isolation guarantees + // of resources (this component's table is shared with no + // others). Here `register_component_entity_type` will find + // imported resources and then `lookup_resource` will find the + // resource within `arg` as necessary to lookup the original + // true definition of this resource. + // + // This is what enables tracking true resource origins + // throughout component translation while simultaneously also + // tracking unique tables for each resource in each component. + let mut path = Vec::new(); + let (resources, types) = types.resources_mut_and_types(); + resources.register_component_entity_type( + &frame.translation.types_ref(), + *ty, + &mut path, + &mut |path| arg.lookup_resource(path, types), + ); + + // And now with all the type information out of the way the + // `arg` definition is moved into its corresponding index space. + frame.push_item(arg.clone()); + } // Lowering a component function to a core wasm function is // generally what "triggers compilation". Here various metadata is @@ -407,7 +497,9 @@ impl<'a> Inliner<'a> { canonical_abi, lower_ty, } => { - let options_lower = self.adapter_options(frame, options); + let lower_ty = + types.convert_component_func_type(frame.translation.types_ref(), *lower_ty)?; + let options_lower = self.adapter_options(frame, types, options); let func = match &frame.component_funcs[*func] { // If this component function was originally a host import // then this is a lowered host function which needs a @@ -420,6 +512,7 @@ impl<'a> Inliner<'a> { canonical_abi: *canonical_abi, import, options, + lower_ty, }); dfg::CoreDef::Lowered(index) } @@ -493,7 +586,7 @@ impl<'a> Inliner<'a> { let adapter_idx = self.result.adapters.push_uniq(Adapter { lift_ty: *lift_ty, lift_options: options_lift.clone(), - lower_ty: *lower_ty, + lower_ty, lower_options: options_lower, func: func.clone(), }); @@ -507,14 +600,65 @@ impl<'a> Inliner<'a> { // some metadata about the lifting is simply recorded. This'll get // plumbed through to exports or a fused adapter later on. Lift(ty, func, options) => { - let options = self.adapter_options(frame, options); + let ty = types.convert_component_func_type(frame.translation.types_ref(), *ty)?; + let options = self.adapter_options(frame, types, options); frame.component_funcs.push(ComponentFuncDef::Lifted { - ty: *ty, + ty, func: frame.funcs[*func].clone(), options, }); } + // A new resource type is being introduced, so it's recorded as a + // brand new resource in the final `resources` array. Additionally + // for now resource introductions are considered side effects to + // know when to register their destructors so that's recorded as + // well. + // + // Note that this has the effect of when a component is instantiated + // twice it will produce unique types for the resources from each + // instantiation. That's the intended runtime semantics and + // implementation here, however. + Resource(ty, rep, dtor) => { + let idx = self.result.resources.push(dfg::Resource { + rep: *rep, + dtor: dtor.map(|i| frame.funcs[i].clone()), + instance: frame.instance, + }); + self.result + .side_effects + .push(dfg::SideEffect::Resource(idx)); + + // Register with type translation that all future references to + // `ty` will refer to `idx`. + // + // Note that this registration information is lost when this + // component finishes instantiation due to the snapshotting + // behavior in the frame processing loop above. This is also + // intended, though, since `ty` can't be referred to outside of + // this component. + let idx = self.result.resource_index(idx); + types + .resources_mut() + .register_resource(frame.translation.types_ref(), *ty, idx); + } + + // Resource-related intrinsics are generally all the same. + // Wasmparser type information is converted to wasmtime type + // information and then new entries for each intrinsic are recorded. + ResourceNew(id, ty) => { + let id = types.resource_id(frame.translation.types_ref(), *id); + frame.funcs.push(dfg::CoreDef::ResourceNew(id, *ty)); + } + ResourceRep(id, ty) => { + let id = types.resource_id(frame.translation.types_ref(), *id); + frame.funcs.push(dfg::CoreDef::ResourceRep(id, *ty)); + } + ResourceDrop(id, ty) => { + let id = types.resource_id(frame.translation.types_ref(), *id); + frame.funcs.push(dfg::CoreDef::ResourceDrop(id, *ty)); + } + ModuleStatic(idx) => { frame.modules.push(ModuleDef::Static(*idx)); } @@ -546,7 +690,7 @@ impl<'a> Inliner<'a> { } ModuleDef::Import(path, ty) => { let mut defs = IndexMap::new(); - for ((module, name), _) in self.types[*ty].imports.iter() { + for ((module, name), _) in types[*ty].imports.iter() { let instance = args[module.as_str()]; let def = self.core_def_of_module_instance_export(frame, instance, name); @@ -561,6 +705,9 @@ impl<'a> Inliner<'a> { }; let idx = self.result.instances.push(init); + self.result + .side_effects + .push(dfg::SideEffect::Instance(idx)); let idx2 = self.runtime_instances.push(instance_module); assert_eq!(idx, idx2); frame @@ -606,7 +753,7 @@ impl<'a> Inliner<'a> { // of this entire module, so the "easy" step here is to simply // create a new inliner frame and return it to get pushed onto the // stack. - ComponentInstantiate(component, args) => { + ComponentInstantiate(component, args, ty) => { let component: &ComponentDef<'a> = &frame.components[*component]; let index = RuntimeComponentInstanceIndex::from_u32( self.result.num_runtime_component_instances, @@ -617,8 +764,9 @@ impl<'a> Inliner<'a> { &self.nested_components[component.index], component.closure.clone(), args.iter() - .map(|(name, item)| (*name, frame.item(*item))) - .collect(), + .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) + .collect::>()?, + Some(*ty), ); return Ok(Some(frame)); } @@ -626,8 +774,8 @@ impl<'a> Inliner<'a> { ComponentSynthetic(map) => { let items = map .iter() - .map(|(name, index)| (*name, frame.item(*index))) - .collect(); + .map(|(name, index)| Ok((*name, frame.item(*index, types)?))) + .collect::>()?; frame .component_instances .push(ComponentInstanceDef::Items(items)); @@ -677,59 +825,16 @@ impl<'a> Inliner<'a> { // with the clone + push here. Afterwards an appropriate // item is then pushed in the relevant index space. ComponentInstanceDef::Import(path, ty) => { - let mut path = path.clone(); - path.path.push(name); - match self.types[*ty].exports[*name] { - TypeDef::ComponentFunc(_) => { - frame.component_funcs.push(ComponentFuncDef::Import(path)); - } - TypeDef::ComponentInstance(ty) => { - frame - .component_instances - .push(ComponentInstanceDef::Import(path, ty)); - } - TypeDef::Module(ty) => { - frame.modules.push(ModuleDef::Import(path, ty)); - } - TypeDef::Component(_) => { - unimplemented!("aliasing component export of component import") - } - - // This is handled during the initial translation - // pass and doesn't need further handling here. - TypeDef::Interface(_) => {} - - // not possible with valid components - TypeDef::CoreFunc(_) => unreachable!(), - } + let path = path.push(*name); + let def = ComponentItemDef::from_import(path, types[*ty].exports[*name])?; + frame.push_item(def); } // Given a component instance which was either created // through instantiation of a component or through a // synthetic renaming of items we just schlep around the // definitions of various items here. - ComponentInstanceDef::Items(map) => match &map[*name] { - ComponentItemDef::Func(i) => { - frame.component_funcs.push(i.clone()); - } - ComponentItemDef::Module(i) => { - frame.modules.push(i.clone()); - } - ComponentItemDef::Component(i) => { - frame.components.push(i.clone()); - } - ComponentItemDef::Instance(i) => { - let instance = i.clone(); - frame.component_instances.push(instance); - } - - // Like imports creation of types from an `alias`-ed - // export does not, at this time, modify what the type - // is or anything like that. The type structure of the - // component being instantiated is unchanged so types - // are ignored here. - ComponentItemDef::Type(_ty) => {} - }, + ComponentInstanceDef::Items(map) => frame.push_item(map[*name].clone()), } } @@ -837,6 +942,7 @@ impl<'a> Inliner<'a> { fn adapter_options( &mut self, frame: &InlinerFrame<'a>, + types: &ComponentTypesBuilder, options: &LocalCanonicalOptions, ) -> AdapterOptions { let memory = options.memory.map(|i| { @@ -855,7 +961,7 @@ impl<'a> Inliner<'a> { ExportItem::Name(_) => unreachable!(), }, InstanceModule::Import(ty) => match &memory.item { - ExportItem::Name(name) => match self.types[*ty].exports[name] { + ExportItem::Name(name) => match types[*ty].exports[name] { EntityType::Memory(m) => m.memory64, _ => unreachable!(), }, @@ -903,6 +1009,7 @@ impl<'a> Inliner<'a> { &mut self, name: &str, def: ComponentItemDef<'a>, + types: &'a ComponentTypesBuilder, map: &mut IndexMap, ) -> Result<()> { let export = match def { @@ -944,11 +1051,10 @@ impl<'a> Inliner<'a> { // Note that for now this would only work with // module-exporting instances. ComponentInstanceDef::Import(path, ty) => { - for (name, ty) in self.types[ty].exports.iter() { - let mut path = path.clone(); - path.path.push(name); + for (name, ty) in types[ty].exports.iter() { + let path = path.push(name); let def = ComponentItemDef::from_import(path, *ty)?; - self.record_export(name, def, &mut result)?; + self.record_export(name, def, types, &mut result)?; } } @@ -957,7 +1063,7 @@ impl<'a> Inliner<'a> { // the bag of items we're exporting. ComponentInstanceDef::Items(map) => { for (name, def) in map { - self.record_export(name, def, &mut result)?; + self.record_export(name, def, types, &mut result)?; } } } @@ -984,6 +1090,7 @@ impl<'a> InlinerFrame<'a> { translation: &'a Translation<'a>, closure: ComponentClosure<'a>, args: HashMap<&'a str, ComponentItemDef<'a>>, + instance_ty: Option, ) -> Self { // FIXME: should iterate over the initializers of `translation` and // calculate the size of each index space to use `with_capacity` for @@ -994,6 +1101,7 @@ impl<'a> InlinerFrame<'a> { translation, closure, args, + instance_ty, initializers: translation.initializers.iter(), funcs: Default::default(), @@ -1009,15 +1117,58 @@ impl<'a> InlinerFrame<'a> { } } - fn item(&self, index: ComponentItem) -> ComponentItemDef<'a> { - match index { + fn item( + &self, + index: ComponentItem, + types: &mut ComponentTypesBuilder, + ) -> Result> { + Ok(match index { ComponentItem::Func(i) => ComponentItemDef::Func(self.component_funcs[i].clone()), ComponentItem::Component(i) => ComponentItemDef::Component(self.components[i].clone()), ComponentItem::ComponentInstance(i) => { ComponentItemDef::Instance(self.component_instances[i].clone()) } ComponentItem::Module(i) => ComponentItemDef::Module(self.modules[i].clone()), - ComponentItem::Type(t) => ComponentItemDef::Type(t), + ComponentItem::Type(t) => { + let types_ref = self.translation.types_ref(); + ComponentItemDef::Type(types.convert_type(types_ref, t)?) + } + }) + } + + /// Pushes the component `item` definition provided into the appropriate + /// index space within this component. + fn push_item(&mut self, item: ComponentItemDef<'a>) { + match item { + ComponentItemDef::Func(i) => { + self.component_funcs.push(i); + } + ComponentItemDef::Module(i) => { + self.modules.push(i); + } + ComponentItemDef::Component(i) => { + self.components.push(i); + } + ComponentItemDef::Instance(i) => { + self.component_instances.push(i); + } + + // In short, type definitions aren't tracked here. + // + // The longer form explanation for this is that structural types + // like lists and records don't need to be tracked at all and the + // only significant type which needs tracking is resource types + // themselves. Resource types, however, are tracked within the + // `ResourcesBuilder` state rather than an `InlinerFrame` so they're + // ignored here as well. The general reason for that is that type + // information is everywhere and this `InlinerFrame` is not + // everywhere so it seemed like it would make sense to split the + // two. + // + // Note though that this case is actually frequently hit, so it + // can't be `unreachable!()`. Instead callers are responsible for + // handling this appropriately with respect to resources. + ComponentItemDef::Type(_ty) => {} } } @@ -1034,6 +1185,49 @@ impl<'a> InlinerFrame<'a> { ClosedOverComponent::Upvar(i) => self.closure.components[i].clone(), } } + + /// Completes the instantiation of a subcomponent and records type + /// information for the instance that was produced. + /// + /// This method is invoked when an `InlinerFrame` finishes for a + /// subcomponent. The `def` provided represents the instance that was + /// produced from instantiation, and `ty` is the wasmparser-defined type of + /// the instance produced. + /// + /// The purpose of this method is to record type information about resources + /// in the instance produced. In the component model all instantiations of a + /// component produce fresh new types for all resources which are unequal to + /// all prior resources. This means that if wasmparser's `ty` type + /// information references a unique resource within `def` that has never + /// been registered before then that means it's a defined resource within + /// the component that was just instantiated (as opposed to an imported + /// resource which was reexported). + /// + /// Further type translation after this instantiation can refer to these + /// resource types and a mapping from those types to the wasmtime-internal + /// types is required, so this method builds up those mappings. + /// + /// Essentially what happens here is that the `ty` type is registered and + /// any new unique resources are registered so new tables can be introduced + /// along with origin information about the actual underlying resource type + /// and which component instantiated it. + fn finish_instantiate( + &mut self, + def: ComponentInstanceDef<'a>, + ty: TypeId, + types: &mut ComponentTypesBuilder, + ) { + let (resources, types) = types.resources_mut_and_types(); + let mut path = Vec::new(); + let arg = ComponentItemDef::Instance(def); + resources.register_component_entity_type( + &self.translation.types_ref(), + wasmparser::types::ComponentEntityType::Instance(ty), + &mut path, + &mut |path| arg.lookup_resource(path, types), + ); + self.push_item(arg); + } } impl<'a> ImportPath<'a> { @@ -1043,6 +1237,12 @@ impl<'a> ImportPath<'a> { path: Vec::new(), } } + + fn push(&self, s: impl Into>) -> ImportPath<'a> { + let mut new = self.clone(); + new.path.push(s.into()); + new + } } impl<'a> ComponentItemDef<'a> { @@ -1056,11 +1256,54 @@ impl<'a> ComponentItemDef<'a> { // FIXME(#4283) should commit one way or another to how this // should be treated. TypeDef::Component(_ty) => bail!("root-level component imports are not supported"), - TypeDef::Interface(ty) => ComponentItemDef::Type(TypeDef::Interface(ty)), + TypeDef::Interface(_) | TypeDef::Resource(_) => ComponentItemDef::Type(ty), TypeDef::CoreFunc(_ty) => unreachable!(), }; Ok(item) } + + /// Walks the `path` within `self` to find a resource at that path. + /// + /// This method is used when resources are found within wasmparser's type + /// information and they need to be correlated with actual concrete + /// definitions from this inlining pass. The `path` here is a list of + /// instance export names (or empty) to walk to reach down into the final + /// definition which should refer to a resource itself. + fn lookup_resource(&self, path: &[&str], types: &ComponentTypes) -> ResourceIndex { + let mut cur = self.clone(); + + // Each element of `path` represents unwrapping a layer of an instance + // type, so handle those here by updating `cur` iteratively. + for element in path.iter().copied() { + let instance = match cur { + ComponentItemDef::Instance(def) => def, + _ => unreachable!(), + }; + cur = match instance { + // If this instance is a "bag of things" then this is as easy as + // looking up the name in the bag of names. + ComponentInstanceDef::Items(names) => names[element].clone(), + + // If, however, this instance is an imported instance then this + // is a further projection within the import with one more path + // element. The `types` type information is used to lookup the + // type of `element` within the instance type, and that's used + // in conjunction with a one-longer `path` to produce a new item + // definition. + ComponentInstanceDef::Import(path, ty) => { + ComponentItemDef::from_import(path.push(element), types[ty].exports[element]) + .unwrap() + } + }; + } + + // Once `path` has been iterated over it must be the case that the final + // item is a resource type, in which case a lookup can be performed. + match cur { + ComponentItemDef::Type(TypeDef::Resource(idx)) => types[idx].ty, + _ => unreachable!(), + } + } } enum InstanceModule { diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index afcca9ee560e..d42dbc2c304c 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -1,7 +1,7 @@ use crate::component::{MAX_FLAT_PARAMS, MAX_FLAT_RESULTS}; use crate::{ EntityType, ModuleTypes, ModuleTypesBuilder, PrimaryMap, SignatureIndex, TypeConvert, - WasmHeapType, + WasmHeapType, WasmType, }; use anyhow::{bail, Result}; use cranelift_entity::EntityRef; @@ -16,6 +16,9 @@ use wasmtime_component_util::{DiscriminantSize, FlagsSize}; pub use wasmtime_types::StaticModuleIndex; +mod resources; +pub use resources::ResourcesBuilder; + /// Maximum nesting depth of a type allowed in Wasmtime. /// /// This constant isn't chosen via any scientific means and its main purpose is @@ -108,6 +111,43 @@ indices! { /// Index pointing to a list type in the component model. pub struct TypeListIndex(u32); + /// Index pointing to a resource table within a component. + /// + /// This is a Wasmtime-specific type index which isn't part of the component + /// model per-se (or at least not the binary format). This index represents + /// a pointer to a table of runtime information tracking state for resources + /// within a component. Tables are generated per-resource-per-component + /// meaning that if the exact same resource is imported into 4 subcomponents + /// then that's 5 tables: one for the defining component and one for each + /// subcomponent. + /// + /// All resource-related intrinsics operate on table-local indices which + /// indicate which table the intrinsic is modifying. Each resource table has + /// an origin resource type (defined by `ResourceIndex`) along with a + /// component instance that it's recorded for. + pub struct TypeResourceTableIndex(u32); + + /// Index pointing to a resource within a component. + /// + /// This index space covers all unique resource type definitions. For + /// example all unique imports come first and then all locally-defined + /// resources come next. Note that this does not count the number of runtime + /// tables required to track resources (that's `TypeResourceTableIndex` + /// instead). Instead this is a count of the number of unique + /// `(type (resource (rep ..)))` declarations within a component, plus + /// imports. + /// + /// This is then used for correlating various information such as + /// destructors, origin information, etc. + pub struct ResourceIndex(u32); + + /// Index pointing to a local resource defined within a component. + /// + /// This is similar to `FooIndex` and `DefinedFooIndex` for core wasm and + /// the idea here is that this is guaranteed to be a wasm-defined resource + /// which is connected to a component instance for example. + pub struct DefinedResourceIndex(u32); + // ======================================================================== // Index types used to identify modules and components during compilation. @@ -178,6 +218,16 @@ indices! { /// This is used to index the `VMFuncRef` slots reserved for string encoders /// which reference linear memories defined within a component. pub struct RuntimeTranscoderIndex(u32); + + /// Index into the list of `resource.new` intrinsics used by a component. + /// + /// This is used to allocate space in `VMComponentContext` and record + /// `VMFuncRef`s corresponding to the definition of the intrinsic. + pub struct RuntimeResourceNewIndex(u32); + /// Same as `RuntimeResourceNewIndex`, but for `resource.rep` + pub struct RuntimeResourceDropIndex(u32); + /// Same as `RuntimeResourceNewIndex`, but for `resource.drop` + pub struct RuntimeResourceRepIndex(u32); } // Reexport for convenience some core-wasm indices which are also used in the @@ -186,14 +236,14 @@ pub use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex}; /// Equivalent of `EntityIndex` but for the component model instead of core /// wasm. -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy)] #[allow(missing_docs)] pub enum ComponentItem { Func(ComponentFuncIndex), Module(ModuleIndex), Component(ComponentIndex), ComponentInstance(ComponentInstanceIndex), - Type(TypeDef), + Type(wasmparser::types::TypeId), } /// Runtime information about the type information contained within a component. @@ -216,6 +266,7 @@ pub struct ComponentTypes { unions: PrimaryMap, options: PrimaryMap, results: PrimaryMap, + resource_tables: PrimaryMap, module_types: ModuleTypes, } @@ -238,7 +289,9 @@ impl ComponentTypes { InterfaceType::U32 | InterfaceType::S32 | InterfaceType::Float32 - | InterfaceType::Char => &CanonicalAbiInfo::SCALAR4, + | InterfaceType::Char + | InterfaceType::Own(_) + | InterfaceType::Borrow(_) => &CanonicalAbiInfo::SCALAR4, InterfaceType::U64 | InterfaceType::S64 | InterfaceType::Float64 => { &CanonicalAbiInfo::SCALAR8 @@ -256,6 +309,25 @@ impl ComponentTypes { InterfaceType::Result(i) => &self[*i].abi, } } + + /// Smaller helper method to find a `SignatureIndex` which corresponds to + /// the `resource.drop` intrinsic in components, namely a core wasm function + /// type which takes one `i32` argument and has no results. + /// + /// This is a bit of a hack right now as ideally this find operation + /// wouldn't be needed and instead the `SignatureIndex` itself would be + /// threaded through appropriately, but that's left for a future + /// refactoring. Try not to lean too hard on this method though. + pub fn find_resource_drop_signature(&self) -> Option { + self.module_types + .wasm_signatures() + .find(|(_, sig)| { + sig.params().len() == 1 + && sig.returns().len() == 0 + && sig.params()[0] == WasmType::I32 + }) + .map(|(i, _)| i) + } } macro_rules! impl_index { @@ -283,6 +355,7 @@ impl_index! { impl Index for ComponentTypes { TypeOption => options } impl Index for ComponentTypes { TypeResult => results } impl Index for ComponentTypes { TypeList => lists } + impl Index for ComponentTypes { TypeResourceTable => resource_tables } } // Additionally forward anything that can index `ModuleTypes` to `ModuleTypes` @@ -322,10 +395,7 @@ pub struct ComponentTypesBuilder { // as opposed to `ComponentTypes`. type_info: TypeInformationCache, - defined_types_cache: HashMap, - module_types_cache: HashMap, - component_types_cache: HashMap, - instance_types_cache: HashMap, + resources: ResourcesBuilder, } macro_rules! intern_and_fill_flat_types { @@ -364,13 +434,31 @@ impl ComponentTypesBuilder { &mut self.module_types } + /// Returns the number of resource tables allocated so far, or the maximum + /// `TypeResourceTableIndex`. + pub fn num_resource_tables(&self) -> usize { + self.component_types.resource_tables.len() + } + + /// Returns a mutable reference to the underlying `ResourcesBuilder`. + pub fn resources_mut(&mut self) -> &mut ResourcesBuilder { + &mut self.resources + } + + /// Work around the borrow checker to borrow two sub-fields simultaneously + /// externally. + pub fn resources_mut_and_types(&mut self) -> (&mut ResourcesBuilder, &ComponentTypes) { + (&mut self.resources, &self.component_types) + } + /// Converts a wasmparser `ComponentFuncType` into Wasmtime's type /// representation. pub fn convert_component_func_type( &mut self, types: types::TypesRef<'_>, - ty: &types::ComponentFuncType, + id: types::TypeId, ) -> Result { + let ty = types[id].unwrap_component_func(); let params = ty .params .iter() @@ -406,12 +494,11 @@ impl ComponentTypesBuilder { TypeDef::ComponentInstance(self.convert_instance(types, id)?) } types::ComponentEntityType::Func(id) => { - let id = types[id].unwrap_component_func(); - let idx = self.convert_component_func_type(types, id)?; - TypeDef::ComponentFunc(idx) + TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) } - types::ComponentEntityType::Type { created, .. } => match &types[created] { + types::ComponentEntityType::Type { created, .. } => match types[created] { types::Type::Defined(_) => TypeDef::Interface(self.defined_type(types, created)?), + types::Type::Resource(_) => TypeDef::Resource(self.resource_id(types, created)), _ => bail!("unsupported type export"), }, types::ComponentEntityType::Value(_) => bail!("values not supported"), @@ -431,13 +518,13 @@ impl ComponentTypesBuilder { types::Type::ComponentInstance(_) => { TypeDef::ComponentInstance(self.convert_instance(types, id)?) } - types::Type::ComponentFunc(f) => { - TypeDef::ComponentFunc(self.convert_component_func_type(types, f)?) + types::Type::ComponentFunc(_) => { + TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) } types::Type::Instance(_) | types::Type::Sub(_) => { unreachable!() } - types::Type::Resource(_) => unimplemented!(), + types::Type::Resource(_) => TypeDef::Resource(self.resource_id(types, id)), }) } @@ -446,9 +533,6 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - if let Some(ret) = self.component_types_cache.get(&id) { - return Ok(*ret); - } let ty = types[id].unwrap_component(); let mut result = TypeComponent::default(); for (name, ty) in ty.imports.iter() { @@ -463,9 +547,7 @@ impl ComponentTypesBuilder { self.convert_component_entity_type(types, *ty)?, ); } - let ret = self.component_types.components.push(result); - self.component_types_cache.insert(id, ret); - Ok(ret) + Ok(self.component_types.components.push(result)) } fn convert_instance( @@ -473,9 +555,6 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - if let Some(ret) = self.instance_types_cache.get(&id) { - return Ok(*ret); - } let ty = types[id].unwrap_component_instance(); let mut result = TypeComponentInstance::default(); for (name, ty) in ty.exports.iter() { @@ -484,9 +563,7 @@ impl ComponentTypesBuilder { self.convert_component_entity_type(types, *ty)?, ); } - let ret = self.component_types.component_instances.push(result); - self.instance_types_cache.insert(id, ret); - Ok(ret) + Ok(self.component_types.component_instances.push(result)) } fn convert_module( @@ -494,10 +571,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - if let Some(ret) = self.module_types_cache.get(&id) { - return Ok(*ret); - } - let ty = types[id].unwrap_module(); + let ty = &types[id].unwrap_module(); let mut result = TypeModule::default(); for ((module, field), ty) in ty.imports.iter() { result.imports.insert( @@ -510,9 +584,7 @@ impl ComponentTypesBuilder { .exports .insert(name.clone(), self.entity_type(types, ty)?); } - let ret = self.component_types.modules.push(result); - self.module_types_cache.insert(id, ret); - Ok(ret) + Ok(self.component_types.modules.push(result)) } fn entity_type( @@ -538,9 +610,6 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - if let Some(ty) = self.defined_types_cache.get(&id) { - return Ok(*ty); - } let ret = match types[id].unwrap_defined() { types::ComponentDefinedType::Primitive(ty) => ty.into(), types::ComponentDefinedType::Record(e) => { @@ -564,15 +633,15 @@ impl ComponentTypesBuilder { types::ComponentDefinedType::Result { ok, err } => { InterfaceType::Result(self.result_type(types, ok, err)?) } - types::ComponentDefinedType::Own(_) | types::ComponentDefinedType::Borrow(_) => { - unimplemented!("resource types") + types::ComponentDefinedType::Own(r) => InterfaceType::Own(self.resource_id(types, *r)), + types::ComponentDefinedType::Borrow(r) => { + InterfaceType::Borrow(self.resource_id(types, *r)) } }; let info = self.type_information(&ret); if info.depth > MAX_TYPE_DEPTH { bail!("type nesting is too deep"); } - self.defined_types_cache.insert(id, ret); Ok(ret) } @@ -734,6 +803,17 @@ impl ComponentTypesBuilder { Ok(self.add_list_type(TypeList { element })) } + /// Converts a wasmparser `id`, which must point to a resource, to its + /// corresponding `TypeResourceTableIndex`. + pub fn resource_id( + &mut self, + types: types::TypesRef<'_>, + id: types::TypeId, + ) -> TypeResourceTableIndex { + let id = types[id].unwrap_resource(); + self.resources.convert(id, &mut self.component_types) + } + /// Interns a new function type within this type information. pub fn add_func_type(&mut self, ty: TypeFunc) -> TypeFuncIndex { intern(&mut self.functions, &mut self.component_types.functions, ty) @@ -807,7 +887,9 @@ impl ComponentTypesBuilder { | InterfaceType::S16 | InterfaceType::U32 | InterfaceType::S32 - | InterfaceType::Char => { + | InterfaceType::Char + | InterfaceType::Own(_) + | InterfaceType::Borrow(_) => { static INFO: TypeInformation = TypeInformation::primitive(FlatType::I32); &INFO } @@ -891,6 +973,11 @@ pub enum TypeDef { Module(TypeModuleIndex), /// A core wasm function using only core wasm types. CoreFunc(SignatureIndex), + /// A resource type which operates on the specified resource table. + /// + /// Note that different resource tables may point to the same underlying + /// actual resource type, but that's a private detail. + Resource(TypeResourceTableIndex), } // NB: Note that maps below are stored as an `IndexMap` now but the order @@ -979,6 +1066,8 @@ pub enum InterfaceType { Union(TypeUnionIndex), Option(TypeOptionIndex), Result(TypeResultIndex), + Own(TypeResourceTableIndex), + Borrow(TypeResourceTableIndex), } impl From<&wasmparser::PrimitiveValType> for InterfaceType { @@ -1459,6 +1548,19 @@ pub struct TypeResult { pub info: VariantInfo, } +/// Metadata about a resource table added to a component. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeResourceTable { + /// The original resource that this table contains. + /// + /// This is used when destroying resources within this table since this + /// original definition will know how to execute destructors. + pub ty: ResourceIndex, + + /// The component instance that contains this resource table. + pub instance: RuntimeComponentInstanceIndex, +} + /// Shape of a "list" interface type. #[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] pub struct TypeList { diff --git a/crates/environ/src/component/types/resources.rs b/crates/environ/src/component/types/resources.rs new file mode 100644 index 000000000000..22a99d62fa8c --- /dev/null +++ b/crates/environ/src/component/types/resources.rs @@ -0,0 +1,240 @@ +//! Implementation of resource type information within Wasmtime. +//! +//! Resource types are one of the trickier parts of the component model. Types +//! such as `list`, `record`, and `string` are considered "structural" where two +//! types are considered equal if their components are equal. For example `(list +//! $a)` and `(list $b)` are the same if `$a` and `$b` are the same. Resources, +//! however, are not as simple. +//! +//! The type of a resource can "change" sort of depending on who you are and how +//! you view it. Some examples of resources are: +//! +//! * When a resource is imported into a component the internal component +//! doesn't know the underlying resource type, but the outer component which +//! performed an instantiation knows that. This means that if a component +//! imports two unique resources but is instantiated with two copies of the +//! same resource the internal component can't know they're the same but the +//! outer component knows they're the same. +//! +//! * Each instantiation of a component produces new resource types. This means +//! that if a component instantiates a subcomponent twice then the resources +//! defined in that subcomponent are considered different between the two +//! instances. +//! +//! All this is basically to say that resources require special care. The +//! purpose of resources are to provide isolation across component boundaries +//! and strict guarantees around ownership and borrowing. Getting the type +//! information wrong can compromise on all of these guarantees which is +//! something Wasmtime would ideally avoid. +//! +//! ## Interaction with `wasmparser` +//! +//! The trickiness of resource types is not unique of Wasmtime and the first +//! line of translating a component, `wasmparser`, already has quite a lot of +//! support for handling all the various special cases of resources. Namely +//! `wasmparser` has a `ResourceId` type which can be used to test whether two +//! resources are the same or unique. For example in the above scenario where a +//! component imports two resources then within that component they'll have +//! unique ids. Externally though the outer component will be able to see that +//! the ids are the same. +//! +//! Given the subtlety here the goal is to lean on `wasmparser` as much as +//! possible for this information. The thinking is "well it got things right so +//! let's not duplicate". This is one of the motivations for plumbing +//! `wasmparser`'s type information throughout `LocalInitializer` structures +//! during translation of a component. During conversion to a +//! `GlobalInitializer` is where everything is boiled away. +//! +//! ## Converting to Wasmtime +//! +//! The purpose of this module then is to convert `wasmparser`'s view of +//! resources into Wasmtime's view of resources. Wasmtime's goal is to +//! determine how many tables are required for each resource within a component +//! and then from then on purely talk about table indices. Each component +//! instance will require a table per-resource and this figures that all out. +//! +//! The conversion process, however, is relatively tightly intertwined with type +//! conversion in general. The "leaves" of a type may be resources but there are +//! other structures in a type such as lists, records, variants, etc. This means +//! that the `ResourcesBuilder` below is embedded within a +//! `ComponentTypesBuilder`. This also means that it's unfortuantely not easy to +//! disentangle pieces and have one nice standalone file that handles everything +//! related to type information about resources. Instead this one file was +//! chosen as the place for this doc comment but the file itself is deceptively +//! small as much of the other handling happens elsewhere in component +//! translation. +//! +//! For more details on fiddly bits see the documentation on various fields and +//! methods below. + +use crate::component::{ + ComponentTypes, ResourceIndex, RuntimeComponentInstanceIndex, TypeResourceTable, + TypeResourceTableIndex, +}; +use std::collections::HashMap; +use wasmparser::types; + +/// Builder state used to translate wasmparser's `ResourceId` types to +/// Wasmtime's `TypeResourceTableIndex` type. +/// +/// This is contained in a `ComponentTypesBuilder` but is modified quite a bit +/// manually via the `inline` phase of component instantiation. +/// +/// This type crucially implements the `Clone` trait which is used to "snapshot" +/// the current state of resource translation. The purpose of `Clone` here is to +/// record translation information just before a subcomponent is instantiated to +/// restore it after the subcomponent's instantiation has completed. This is +/// done to handle instantiations of the same component multiple times +/// correctly. +/// +/// Wasmparser produces one set of type information for a component, and not a +/// unique set of type information about its internals for each instantiation. +/// Each instance which results from instantiation gets a new type, but when +/// we're translating the instantiation of a component Wasmtime will re-run all +/// initializers. This means that if naively implemented the `ResourceId` +/// mapping from the first instantiation will be reused by the second +/// instantiation. The snapshotting behavior and restoration guarantees that +/// whenever a subcomponent is visited and instantiated it's guaranteed that +/// there's no registered information for its `ResourceId` definitions within +/// this builder. +/// +/// Note that `ResourceId` references are guaranteed to be "local" in the sense +/// that if a resource is defined within a component then the ID it's assigned +/// internally within a component is different than the ID when it's +/// instantiated (since all instantiations produce new types). This means that +/// when throwing away state built-up from within a component that's also +/// reasonable because the information should never be used after a component is +/// left anyway. +#[derive(Default, Clone)] +pub struct ResourcesBuilder { + /// A cache of previously visited `ResourceId` items and which table they + /// correspond to. This is lazily populated as resources are visited and is + /// exclusively used by the `convert` function below. + resource_id_to_table_index: HashMap, + + /// A cache of the origin resource type behind a `ResourceId`. + /// + /// Unlike `resource_id_to_table_index` this is required to be eagerly + /// populated before translation of a type occurs. This is populated by + /// `register_*` methods below and is manually done during the `inline` + /// phase. This is used to record the actual underlying type of a resource + /// and where it originally comes from. When a resource is later referred to + /// then a table is injected to be referred to. + resource_id_to_resource_index: HashMap, + + /// The current instance index that's being visited. This is updated as + /// inliner frames are processed and components are instantiated. + current_instance: Option, +} + +impl ResourcesBuilder { + /// Converts the `id` provided into a `TypeResourceTableIndex`. + /// + /// If `id` has previously been seen or converted, the prior value is + /// returned. Otherwise the `resource_id_to_resource_index` table must have + /// been previously populated and additionally `current_instance` must have + /// been previously set. Using these a new `TypeResourceTable` value is + /// allocated which produces a fresh `TypeResourceTableIndex` within the + /// `types` provided. + /// + /// Due to `wasmparser`'s uniqueness of resource IDs combined with the + /// snapshotting and restoration behavior of `ResourcesBuilder` itself this + /// should have the net effect of the first time a resource is seen within + /// any component it's assigned a new table, which is exactly what we want. + pub fn convert( + &mut self, + id: types::ResourceId, + types: &mut ComponentTypes, + ) -> TypeResourceTableIndex { + *self + .resource_id_to_table_index + .entry(id) + .or_insert_with(|| { + let ty = self.resource_id_to_resource_index[&id]; + let instance = self.current_instance.expect("current instance not set"); + types + .resource_tables + .push(TypeResourceTable { ty, instance }) + }) + } + + /// Walks over the `ty` provided, as defined within `types`, and registers + /// all the defined resources found with the `register` function provided. + /// + /// This is used to register `ResourceIndex` entries within the + /// `resource_id_to_resource_index` table of this type for situations such + /// as when a resource is imported into a component. During the inlining + /// phase of compilation the actual underlying type of the resource is + /// known due to tracking dataflow and this registers that relationship. + /// + /// The `path` provided is temporary storage to pass to the `register` + /// function eventually. + /// + /// The `register` callback is invoked with `path` with a list of names + /// which correspond to exports of instances to reach the "leaf" where a + /// resource type is expected. + pub fn register_component_entity_type<'a>( + &mut self, + types: &'a types::TypesRef<'_>, + ty: types::ComponentEntityType, + path: &mut Vec<&'a str>, + register: &mut dyn FnMut(&[&'a str]) -> ResourceIndex, + ) { + match ty { + // If `ty` is itself a type, and that's a resource type, then this + // is where registration happens. The `register` callback is invoked + // with the current path and that's inserted in to + // `resource_id_to_resource_index` if the resource hasn't been seen + // yet. + types::ComponentEntityType::Type { created, .. } => { + let id = match types[created] { + types::Type::Resource(id) => id, + _ => return, + }; + self.resource_id_to_resource_index + .entry(id) + .or_insert_with(|| register(path)); + } + + // Resources can be imported/defined through exports of instances so + // all instance exports are walked here. Note the management of + // `path` which is used for the recursive invocation of this method. + types::ComponentEntityType::Instance(id) => { + let ty = types[id].unwrap_component_instance(); + for (name, ty) in ty.exports.iter() { + path.push(name); + self.register_component_entity_type(types, *ty, path, register); + path.pop(); + } + } + + // None of these items can introduce a new component type, so + // there's no need to recurse over these. + types::ComponentEntityType::Func(_) + | types::ComponentEntityType::Module(_) + | types::ComponentEntityType::Component(_) + | types::ComponentEntityType::Value(_) => {} + } + } + + /// Declares that the wasmparser `id`, which must point to a resource, is + /// defined by the `ty` provided. + /// + /// This is used when a local resource is defined within a component for example. + pub fn register_resource( + &mut self, + types: types::TypesRef<'_>, + id: types::TypeId, + ty: ResourceIndex, + ) { + let id = types[id].unwrap_resource(); + let prev = self.resource_id_to_resource_index.insert(id, ty); + assert!(prev.is_none()); + } + + /// Updates the `current_instance` field to assign instance fields of future + /// `TypeResourceTableIndex` values produced via `convert`. + pub fn set_current_instance(&mut self, instance: RuntimeComponentInstanceIndex) { + self.current_instance = Some(instance); + } +} diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs index e8d52e8f51a9..11650c9c3b76 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -2,23 +2,24 @@ // // struct VMComponentContext { // magic: u32, -// transcode_libcalls: &'static VMBuiltinTranscodeArray, +// libcalls: &'static VMComponentLibcalls, // store: *mut dyn Store, // limits: *const VMRuntimeLimits, // flags: [VMGlobalDefinition; component.num_runtime_component_instances], // lowering_func_refs: [VMFuncRef; component.num_lowerings], // always_trap_func_refs: [VMFuncRef; component.num_always_trap], // transcoder_func_refs: [VMFuncRef; component.num_transcoders], +// resource_new_func_refs: [VMFuncRef; component.num_resource_new], +// resource_rep_func_refs: [VMFuncRef; component.num_resource_rep], +// resource_drop_func_refs: [VMFuncRef; component.num_resource_drop], // lowerings: [VMLowering; component.num_lowerings], // memories: [*mut VMMemoryDefinition; component.num_memories], // reallocs: [*mut VMFuncRef; component.num_reallocs], // post_returns: [*mut VMFuncRef; component.num_post_returns], +// resource_destructors: [*mut VMFuncRef; component.num_resources], // } -use crate::component::{ - Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex, - RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, RuntimeTranscoderIndex, -}; +use crate::component::*; use crate::PtrSize; /// Equivalent of `VMCONTEXT_MAGIC` except for components. @@ -61,20 +62,32 @@ pub struct VMComponentOffsets

{ pub num_always_trap: u32, /// Number of transcoders needed for string conversion. pub num_transcoders: u32, + /// Number of `resource.new` intrinsics within a component. + pub num_resource_new: u32, + /// Number of `resource.rep` intrinsics within a component. + pub num_resource_rep: u32, + /// Number of `resource.drop` intrinsics within a component. + pub num_resource_drop: u32, + /// Number of resources within a component which need destructors stored. + pub num_resources: u32, // precalculated offsets of various member fields magic: u32, - transcode_libcalls: u32, + libcalls: u32, store: u32, limits: u32, flags: u32, lowering_func_refs: u32, always_trap_func_refs: u32, transcoder_func_refs: u32, + resource_new_func_refs: u32, + resource_rep_func_refs: u32, + resource_drop_func_refs: u32, lowerings: u32, memories: u32, reallocs: u32, post_returns: u32, + resource_destructors: u32, size: u32, } @@ -100,18 +113,26 @@ impl VMComponentOffsets

{ .unwrap(), num_always_trap: component.num_always_trap, num_transcoders: component.num_transcoders, + num_resource_new: component.num_resource_new, + num_resource_rep: component.num_resource_rep, + num_resource_drop: component.num_resource_drop, + num_resources: component.num_resources, magic: 0, - transcode_libcalls: 0, + libcalls: 0, store: 0, limits: 0, flags: 0, lowering_func_refs: 0, always_trap_func_refs: 0, transcoder_func_refs: 0, + resource_new_func_refs: 0, + resource_rep_func_refs: 0, + resource_drop_func_refs: 0, lowerings: 0, memories: 0, reallocs: 0, post_returns: 0, + resource_destructors: 0, size: 0, }; @@ -142,7 +163,7 @@ impl VMComponentOffsets

{ fields! { size(magic) = 4u32, align(u32::from(ret.ptr.size())), - size(transcode_libcalls) = ret.ptr.size(), + size(libcalls) = ret.ptr.size(), size(store) = cmul(2, ret.ptr.size()), size(limits) = ret.ptr.size(), align(16), @@ -151,10 +172,14 @@ impl VMComponentOffsets

{ size(lowering_func_refs) = cmul(ret.num_lowerings, ret.ptr.size_of_vm_func_ref()), size(always_trap_func_refs) = cmul(ret.num_always_trap, ret.ptr.size_of_vm_func_ref()), size(transcoder_func_refs) = cmul(ret.num_transcoders, ret.ptr.size_of_vm_func_ref()), + size(resource_new_func_refs) = cmul(ret.num_resource_new, ret.ptr.size_of_vm_func_ref()), + size(resource_rep_func_refs) = cmul(ret.num_resource_rep, ret.ptr.size_of_vm_func_ref()), + size(resource_drop_func_refs) = cmul(ret.num_resource_drop, ret.ptr.size_of_vm_func_ref()), size(lowerings) = cmul(ret.num_lowerings, ret.ptr.size() * 2), size(memories) = cmul(ret.num_runtime_memories, ret.ptr.size()), size(reallocs) = cmul(ret.num_runtime_reallocs, ret.ptr.size()), size(post_returns) = cmul(ret.num_runtime_post_returns, ret.ptr.size()), + size(resource_destructors) = cmul(ret.num_resources, ret.ptr.size()), } ret.size = next_field_offset; @@ -179,10 +204,10 @@ impl VMComponentOffsets

{ self.magic } - /// The offset of the `transcode_libcalls` field. + /// The offset of the `libcalls` field. #[inline] - pub fn transcode_libcalls(&self) -> u32 { - self.transcode_libcalls + pub fn libcalls(&self) -> u32 { + self.libcalls } /// The offset of the `flags` field. @@ -243,6 +268,45 @@ impl VMComponentOffsets

{ self.transcoder_func_refs() + index.as_u32() * u32::from(self.ptr.size_of_vm_func_ref()) } + /// The offset of the `resource_new_func_refs` field. + #[inline] + pub fn resource_new_func_refs(&self) -> u32 { + self.resource_new_func_refs + } + + /// The offset of `VMFuncRef` for the `index` specified. + #[inline] + pub fn resource_new_func_ref(&self, index: RuntimeResourceNewIndex) -> u32 { + assert!(index.as_u32() < self.num_resource_new); + self.resource_new_func_refs() + index.as_u32() * u32::from(self.ptr.size_of_vm_func_ref()) + } + + /// The offset of the `resource_rep_func_refs` field. + #[inline] + pub fn resource_rep_func_refs(&self) -> u32 { + self.resource_rep_func_refs + } + + /// The offset of `VMFuncRef` for the `index` specified. + #[inline] + pub fn resource_rep_func_ref(&self, index: RuntimeResourceRepIndex) -> u32 { + assert!(index.as_u32() < self.num_resource_rep); + self.resource_rep_func_refs() + index.as_u32() * u32::from(self.ptr.size_of_vm_func_ref()) + } + + /// The offset of the `resource_drop_func_refs` field. + #[inline] + pub fn resource_drop_func_refs(&self) -> u32 { + self.resource_drop_func_refs + } + + /// The offset of `VMFuncRef` for the `index` specified. + #[inline] + pub fn resource_drop_func_ref(&self, index: RuntimeResourceDropIndex) -> u32 { + assert!(index.as_u32() < self.num_resource_drop); + self.resource_drop_func_refs() + index.as_u32() * u32::from(self.ptr.size_of_vm_func_ref()) + } + /// The offset of the `lowerings` field. #[inline] pub fn lowerings(&self) -> u32 { @@ -328,6 +392,20 @@ impl VMComponentOffsets

{ self.runtime_post_returns() + index.as_u32() * u32::from(self.ptr.size()) } + /// The offset of the base of the `resource_destructors` field + #[inline] + pub fn resource_destructors(&self) -> u32 { + self.resource_destructors + } + + /// The offset of the `*mut VMFuncRef` for the runtime index + /// provided. + #[inline] + pub fn resource_destructor(&self, index: ResourceIndex) -> u32 { + assert!(index.as_u32() < self.num_resources); + self.resource_destructors() + index.as_u32() * u32::from(self.ptr.size()) + } + /// Return the size of the `VMComponentContext` allocation. #[inline] pub fn size_of_vmctx(&self) -> u32 { diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index bd2b9cdc29f3..9b7fd6e1806f 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -18,8 +18,8 @@ use crate::component::{ CanonicalAbiInfo, ComponentTypesBuilder, FlatType, InterfaceType, StringEncoding, TypeEnumIndex, TypeFlagsIndex, TypeListIndex, TypeOptionIndex, TypeRecordIndex, - TypeResultIndex, TypeTupleIndex, TypeUnionIndex, TypeVariantIndex, VariantInfo, FLAG_MAY_ENTER, - FLAG_MAY_LEAVE, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, + TypeResourceTableIndex, TypeResultIndex, TypeTupleIndex, TypeUnionIndex, TypeVariantIndex, + VariantInfo, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, }; use crate::fact::signature::Signature; use crate::fact::transcode::{FixedEncoding as FE, Transcode, Transcoder}; @@ -554,6 +554,9 @@ impl Compiler<'_, '_> { // 2 cases to consider for each of these variants. InterfaceType::Option(_) | InterfaceType::Result(_) => 2, + + // TODO(#6696) - something nonzero, is 1 right? + InterfaceType::Own(_) | InterfaceType::Borrow(_) => 1, }; match self.fuel.checked_sub(cost) { @@ -587,6 +590,8 @@ impl Compiler<'_, '_> { InterfaceType::Enum(t) => self.translate_enum(*t, src, dst_ty, dst), InterfaceType::Option(t) => self.translate_option(*t, src, dst_ty, dst), InterfaceType::Result(t) => self.translate_result(*t, src, dst_ty, dst), + InterfaceType::Own(t) => self.translate_own(*t, src, dst_ty, dst), + InterfaceType::Borrow(t) => self.translate_borrow(*t, src, dst_ty, dst), } } @@ -2447,6 +2452,38 @@ impl Compiler<'_, '_> { } } + fn translate_own( + &mut self, + src_ty: TypeResourceTableIndex, + src: &Source<'_>, + dst_ty: &InterfaceType, + dst: &Destination, + ) { + let dst_ty = match dst_ty { + InterfaceType::Own(t) => t, + _ => panic!("expected an `Own`"), + }; + + let _ = (src_ty, src, dst_ty, dst); + todo!("TODO: #6696"); + } + + fn translate_borrow( + &mut self, + src_ty: TypeResourceTableIndex, + src: &Source<'_>, + dst_ty: &InterfaceType, + dst: &Destination, + ) { + let dst_ty = match dst_ty { + InterfaceType::Borrow(t) => t, + _ => panic!("expected an `Borrow`"), + }; + + let _ = (src_ty, src, dst_ty, dst); + todo!("TODO: #6696"); + } + fn trap_if_not_flag(&mut self, flags_global: GlobalIndex, flag_to_test: i32, trap: Trap) { self.instruction(GlobalGet(flags_global.as_u32())); self.instruction(I32Const(flag_to_test)); diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs index c65185abb9a5..fd714851006a 100644 --- a/crates/environ/src/trap_encoding.rs +++ b/crates/environ/src/trap_encoding.rs @@ -90,6 +90,12 @@ pub enum Trap { /// Call to a null reference. NullReference, + + /// When the `component-model` feature is enabled this trap represents a + /// scenario where one component tried to call another component but it + /// would have violated the reentrance rules of the component model, + /// triggering a trap instead. + CannotEnterComponent, // if adding a variant here be sure to update the `check!` macro below } @@ -113,6 +119,7 @@ impl fmt::Display for Trap { OutOfFuel => "all fuel consumed by WebAssembly", AtomicWaitNonSharedMemory => "atomic wait on non-shared memory", NullReference => "null reference", + CannotEnterComponent => "cannot enter component instance", }; write!(f, "wasm trap: {desc}") } @@ -228,6 +235,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { OutOfFuel AtomicWaitNonSharedMemory NullReference + CannotEnterComponent } if cfg!(debug_assertions) { diff --git a/crates/fuzzing/src/generators/component_types.rs b/crates/fuzzing/src/generators/component_types.rs index 879f59f29e22..36ea589a5b01 100644 --- a/crates/fuzzing/src/generators/component_types.rs +++ b/crates/fuzzing/src/generators/component_types.rs @@ -126,6 +126,9 @@ pub fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrar .collect::>>()?, ) .unwrap(), + + // Resources aren't fuzzed at this time. + Type::Own(_) | Type::Borrow(_) => unreachable!(), }) } diff --git a/crates/misc/component-test-util/src/lib.rs b/crates/misc/component-test-util/src/lib.rs index 46cdfb7964b0..9f463a7ffac9 100644 --- a/crates/misc/component-test-util/src/lib.rs +++ b/crates/misc/component-test-util/src/lib.rs @@ -2,7 +2,7 @@ use anyhow::Result; use arbitrary::Arbitrary; use std::mem::MaybeUninit; use wasmtime::component::__internal::{ - CanonicalAbiInfo, ComponentTypes, InterfaceType, LiftContext, LowerContext, + CanonicalAbiInfo, InstanceType, InterfaceType, LiftContext, LowerContext, }; use wasmtime::component::{ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, Val}; use wasmtime::{AsContextMut, Config, Engine}; @@ -87,7 +87,7 @@ macro_rules! forward_impls { const ABI: CanonicalAbiInfo = <$b as ComponentType>::ABI; #[inline] - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { <$b as ComponentType>::typecheck(ty, types) } } @@ -108,11 +108,11 @@ macro_rules! forward_impls { } unsafe impl Lift for $a { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { Ok(Self(<$b as Lift>::lift(cx, ty, src)?)) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { Ok(Self(<$b as Lift>::load(cx, ty, bytes)?)) } } diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index fc9207f69b98..50b836464965 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -10,26 +10,26 @@ use crate::{ SendSyncPtr, Store, VMArrayCallFunction, VMFuncRef, VMGlobalDefinition, VMMemoryDefinition, VMNativeCallFunction, VMOpaqueContext, VMSharedSignatureIndex, VMWasmCallFunction, ValRaw, }; +use anyhow::Result; use memoffset::offset_of; use sptr::Strict; use std::alloc::{self, Layout}; +use std::any::Any; use std::marker; use std::mem; use std::ops::Deref; use std::ptr::{self, NonNull}; use std::sync::Arc; -use wasmtime_environ::component::{ - Component, ComponentTypes, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex, - RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, RuntimeTranscoderIndex, - StringEncoding, TypeFuncIndex, VMComponentOffsets, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, - FLAG_NEEDS_POST_RETURN, VMCOMPONENT_MAGIC, -}; -use wasmtime_environ::HostPtr; +use wasmtime_environ::component::*; +use wasmtime_environ::{HostPtr, PrimaryMap}; const INVALID_PTR: usize = 0xdead_dead_beef_beef_u64 as usize; +mod resources; mod transcode; +pub use self::resources::{CallContexts, ResourceTable, ResourceTables}; + /// Runtime representation of a component instance and all state necessary for /// the instance itself. /// @@ -49,6 +49,21 @@ pub struct ComponentInstance { /// Runtime type information about this component. runtime_info: Arc, + /// State of resources for all `TypeResourceTableIndex` values for this + /// component. + /// + /// This is paired with other information to create a `ResourceTables` which + /// is how this field is manipulated. + component_resource_tables: PrimaryMap, + + /// Storage for the type information about resources within this component + /// instance. + /// + /// This is actually `Arc>` but that + /// can't be in this crate because `ResourceType` isn't here. Not using `dyn + /// Any` is left as an exercise for a future refactoring. + resource_types: Arc, + /// A zero-sized field which represents the end of the struct for the actual /// `VMComponentContext` to be allocated behind. vmctx: VMComponentContext, @@ -128,6 +143,25 @@ pub struct VMComponentContext { } impl ComponentInstance { + /// Converts the `vmctx` provided into a `ComponentInstance` and runs the + /// provided closure with that instance. + /// + /// # Unsafety + /// + /// This is `unsafe` because `vmctx` cannot be guaranteed to be a valid + /// pointer and it cannot be proven statically that it's safe to get a + /// mutable reference at this time to the instance from `vmctx`. + pub unsafe fn from_vmctx( + vmctx: *mut VMComponentContext, + f: impl FnOnce(&mut ComponentInstance) -> R, + ) -> R { + let ptr = vmctx + .cast::() + .sub(mem::size_of::()) + .cast::(); + f(&mut *ptr) + } + /// Returns the layout corresponding to what would be an allocation of a /// `ComponentInstance` for the `offsets` provided. /// @@ -153,10 +187,17 @@ impl ComponentInstance { alloc_size: usize, offsets: VMComponentOffsets, runtime_info: Arc, + resource_types: Arc, store: *mut dyn Store, ) { assert!(alloc_size >= Self::alloc_layout(&offsets).size()); + let num_tables = runtime_info.component().num_resource_tables; + let mut component_resource_tables = PrimaryMap::with_capacity(num_tables); + for _ in 0..num_tables { + component_resource_tables.push(ResourceTable::default()); + } + ptr::write( ptr.as_ptr(), ComponentInstance { @@ -170,7 +211,9 @@ impl ComponentInstance { ) .unwrap(), ), + component_resource_tables, runtime_info, + resource_types, vmctx: VMComponentContext { _marker: marker::PhantomPinned, }, @@ -203,10 +246,10 @@ impl ComponentInstance { /// for canonical lowering and lifting operations. pub fn instance_flags(&self, instance: RuntimeComponentInstanceIndex) -> InstanceFlags { unsafe { - InstanceFlags( - self.vmctx_plus_offset::(self.offsets.instance_flags(instance)) - .cast_mut(), - ) + let ptr = self + .vmctx_plus_offset::(self.offsets.instance_flags(instance)) + .cast_mut(); + InstanceFlags(SendSyncPtr::new(NonNull::new(ptr).unwrap())) } } @@ -292,6 +335,21 @@ impl ComponentInstance { unsafe { self.func_ref(self.offsets.transcoder_func_ref(idx)) } } + /// Same as `lowering_func_ref` except for the `resource.new` functions. + pub fn resource_new_func_ref(&self, idx: RuntimeResourceNewIndex) -> NonNull { + unsafe { self.func_ref(self.offsets.resource_new_func_ref(idx)) } + } + + /// Same as `lowering_func_ref` except for the `resource.rep` functions. + pub fn resource_rep_func_ref(&self, idx: RuntimeResourceRepIndex) -> NonNull { + unsafe { self.func_ref(self.offsets.resource_rep_func_ref(idx)) } + } + + /// Same as `lowering_func_ref` except for the `resource.drop` functions. + pub fn resource_drop_func_ref(&self, idx: RuntimeResourceDropIndex) -> NonNull { + unsafe { self.func_ref(self.offsets.resource_drop_func_ref(idx)) } + } + unsafe fn func_ref(&self, offset: u32) -> NonNull { let ret = self.vmctx_plus_offset::(offset); debug_assert!( @@ -417,6 +475,66 @@ impl ComponentInstance { } } + /// Same as `set_lowering` but for the resource.new functions. + pub fn set_resource_new( + &mut self, + idx: RuntimeResourceNewIndex, + wasm_call: NonNull, + native_call: NonNull, + array_call: VMArrayCallFunction, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.set_func_ref( + self.offsets.resource_new_func_ref(idx), + wasm_call, + native_call, + array_call, + type_index, + ); + } + } + + /// Same as `set_lowering` but for the resource.rep functions. + pub fn set_resource_rep( + &mut self, + idx: RuntimeResourceRepIndex, + wasm_call: NonNull, + native_call: NonNull, + array_call: VMArrayCallFunction, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.set_func_ref( + self.offsets.resource_rep_func_ref(idx), + wasm_call, + native_call, + array_call, + type_index, + ); + } + } + + /// Same as `set_lowering` but for the resource.drop functions. + pub fn set_resource_drop( + &mut self, + idx: RuntimeResourceDropIndex, + wasm_call: NonNull, + native_call: NonNull, + array_call: VMArrayCallFunction, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.set_func_ref( + self.offsets.resource_drop_func_ref(idx), + wasm_call, + native_call, + array_call, + type_index, + ); + } + } + unsafe fn set_func_ref( &mut self, offset: u32, @@ -436,10 +554,38 @@ impl ComponentInstance { }; } + /// Configures the destructor for a resource at the `idx` specified. + /// + /// This is required to be called for each resource as it's defined within a + /// component during the instantiation process. + pub fn set_resource_destructor( + &mut self, + idx: ResourceIndex, + dtor: Option>, + ) { + unsafe { + let offset = self.offsets.resource_destructor(idx); + debug_assert!(*self.vmctx_plus_offset::(offset) == INVALID_PTR); + *self.vmctx_plus_offset_mut(offset) = dtor; + } + } + + /// Returns the destructor, if any, for `idx`. + /// + /// This is only valid to call after `set_resource_destructor`, or typically + /// after instantiation. + pub fn resource_destructor(&self, idx: ResourceIndex) -> Option> { + unsafe { + let offset = self.offsets.resource_destructor(idx); + debug_assert!(*self.vmctx_plus_offset::(offset) != INVALID_PTR); + *self.vmctx_plus_offset(offset) + } + } + unsafe fn initialize_vmctx(&mut self, store: *mut dyn Store) { *self.vmctx_plus_offset_mut(self.offsets.magic()) = VMCOMPONENT_MAGIC; - *self.vmctx_plus_offset_mut(self.offsets.transcode_libcalls()) = - &transcode::VMBuiltinTranscodeArray::INIT; + *self.vmctx_plus_offset_mut(self.offsets.libcalls()) = + &transcode::VMComponentLibcalls::INIT; *self.vmctx_plus_offset_mut(self.offsets.store()) = store; *self.vmctx_plus_offset_mut(self.offsets.limits()) = (*store).vmruntime_limits(); @@ -447,7 +593,7 @@ impl ComponentInstance { let i = RuntimeComponentInstanceIndex::from_u32(i); let mut def = VMGlobalDefinition::new(); *def.as_i32_mut() = FLAG_MAY_ENTER | FLAG_MAY_LEAVE; - *self.instance_flags(i).0 = def; + *self.instance_flags(i).as_raw() = def; } // In debug mode set non-null bad values to all "pointer looking" bits @@ -474,6 +620,21 @@ impl ComponentInstance { let offset = self.offsets.transcoder_func_ref(i); *self.vmctx_plus_offset_mut(offset) = INVALID_PTR; } + for i in 0..self.offsets.num_resource_new { + let i = RuntimeResourceNewIndex::from_u32(i); + let offset = self.offsets.resource_new_func_ref(i); + *self.vmctx_plus_offset_mut(offset) = INVALID_PTR; + } + for i in 0..self.offsets.num_resource_rep { + let i = RuntimeResourceRepIndex::from_u32(i); + let offset = self.offsets.resource_rep_func_ref(i); + *self.vmctx_plus_offset_mut(offset) = INVALID_PTR; + } + for i in 0..self.offsets.num_resource_drop { + let i = RuntimeResourceDropIndex::from_u32(i); + let offset = self.offsets.resource_drop_func_ref(i); + *self.vmctx_plus_offset_mut(offset) = INVALID_PTR; + } for i in 0..self.offsets.num_runtime_memories { let i = RuntimeMemoryIndex::from_u32(i); let offset = self.offsets.runtime_memory(i); @@ -489,13 +650,107 @@ impl ComponentInstance { let offset = self.offsets.runtime_post_return(i); *self.vmctx_plus_offset_mut(offset) = INVALID_PTR; } + for i in 0..self.offsets.num_resources { + let i = ResourceIndex::from_u32(i); + let offset = self.offsets.resource_destructor(i); + *self.vmctx_plus_offset_mut(offset) = INVALID_PTR; + } } } + /// Returns a reference to the component type information for this instance. + pub fn component(&self) -> &Component { + self.runtime_info.component() + } + /// Returns the type information that this instance is instantiated with. pub fn component_types(&self) -> &Arc { self.runtime_info.component_types() } + + /// Returns a reference to the resource type information as a `dyn Any`. + /// + /// Wasmtime is the one which then downcasts this to the appropriate type. + pub fn resource_types(&self) -> &Arc { + &self.resource_types + } + + /// Returns whether the resource that `ty` points to is owned by the + /// instance that `ty` correspond to. + /// + /// This is used when lowering borrows to skip table management and instead + /// thread through the underlying representation directly. + pub fn resource_owned_by_own_instance(&self, ty: TypeResourceTableIndex) -> bool { + let resource = &self.component_types()[ty]; + let component = self.component(); + let idx = match component.defined_resource_index(resource.ty) { + Some(idx) => idx, + None => return false, + }; + resource.instance == component.defined_resource_instances[idx] + } + + /// Implementation of the `resource.new` intrinsic for `i32` + /// representations. + pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> u32 { + self.resource_tables().resource_new(Some(resource), rep) + } + + /// Implementation of the `resource.rep` intrinsic for `i32` + /// representations. + pub fn resource_rep32(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result { + self.resource_tables().resource_rep(Some(resource), idx) + } + + /// Implementation of the `resource.drop` intrinsic. + pub fn resource_drop( + &mut self, + resource: TypeResourceTableIndex, + idx: u32, + ) -> Result> { + self.resource_tables().resource_drop(Some(resource), idx) + } + + /// NB: this is intended to be a private method. This does not have + /// `host_table` information at this time meaning it's only suitable for + /// working with resources specified to this component which is currently + /// all that this is used for. + /// + /// If necessary though it's possible to enhance the `Store` trait to thread + /// through the relevant information and get `host_table` to be `Some` here. + fn resource_tables(&mut self) -> ResourceTables<'_> { + ResourceTables { + host_table: None, + calls: unsafe { (&mut *self.store()).component_calls() }, + tables: Some(&mut self.component_resource_tables), + } + } + + /// Returns the runtime state of resources associated with this component. + pub fn component_resource_tables( + &mut self, + ) -> &mut PrimaryMap { + &mut self.component_resource_tables + } + + /// Returns the destructor and instance flags for the specified resource + /// table type. + /// + /// This will lookup the origin definition of the `ty` table and return the + /// destructor/flags for that. + pub fn dtor_and_flags( + &self, + ty: TypeResourceTableIndex, + ) -> (Option>, Option) { + let resource = self.component_types()[ty].ty; + let dtor = self.resource_destructor(resource); + let component = self.component(); + let flags = component.defined_resource_index(resource).map(|i| { + let instance = component.defined_resource_instances[i]; + self.instance_flags(instance) + }); + (dtor, flags) + } } impl VMComponentContext { @@ -524,6 +779,7 @@ impl OwnedComponentInstance { /// heap with `malloc` and configures it for the `component` specified. pub fn new( runtime_info: Arc, + resource_types: Arc, store: *mut dyn Store, ) -> OwnedComponentInstance { let component = runtime_info.component(); @@ -541,7 +797,14 @@ impl OwnedComponentInstance { let ptr = alloc::alloc_zeroed(layout) as *mut ComponentInstance; let ptr = NonNull::new(ptr).unwrap(); - ComponentInstance::new_at(ptr, layout.size(), offsets, runtime_info, store); + ComponentInstance::new_at( + ptr, + layout.size(), + offsets, + runtime_info, + resource_types, + store, + ); let ptr = SendSyncPtr::new(ptr); OwnedComponentInstance { ptr } @@ -556,6 +819,11 @@ impl OwnedComponentInstance { &mut *self.ptr.as_ptr() } + /// Returns the underlying component instance's raw pointer. + pub fn instance_ptr(&self) -> *mut ComponentInstance { + self.ptr.as_ptr() + } + /// See `ComponentInstance::set_runtime_memory` pub fn set_runtime_memory(&mut self, idx: RuntimeMemoryIndex, ptr: *mut VMMemoryDefinition) { unsafe { self.instance_mut().set_runtime_memory(idx, ptr) } @@ -626,6 +894,80 @@ impl OwnedComponentInstance { .set_transcoder(idx, wasm_call, native_call, array_call, type_index) } } + + /// See `ComponentInstance::set_resource_new` + pub fn set_resource_new( + &mut self, + idx: RuntimeResourceNewIndex, + wasm_call: NonNull, + native_call: NonNull, + array_call: VMArrayCallFunction, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.instance_mut().set_resource_new( + idx, + wasm_call, + native_call, + array_call, + type_index, + ) + } + } + + /// See `ComponentInstance::set_resource_rep` + pub fn set_resource_rep( + &mut self, + idx: RuntimeResourceRepIndex, + wasm_call: NonNull, + native_call: NonNull, + array_call: VMArrayCallFunction, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.instance_mut().set_resource_rep( + idx, + wasm_call, + native_call, + array_call, + type_index, + ) + } + } + + /// See `ComponentInstance::set_resource_drop` + pub fn set_resource_drop( + &mut self, + idx: RuntimeResourceDropIndex, + wasm_call: NonNull, + native_call: NonNull, + array_call: VMArrayCallFunction, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.instance_mut().set_resource_drop( + idx, + wasm_call, + native_call, + array_call, + type_index, + ) + } + } + + /// See `ComponentInstance::set_resource_destructor` + pub fn set_resource_destructor( + &mut self, + idx: ResourceIndex, + dtor: Option>, + ) { + unsafe { self.instance_mut().set_resource_destructor(idx, dtor) } + } + + /// See `ComponentInstance::resource_types` + pub fn resource_types_mut(&mut self) -> &mut Arc { + unsafe { &mut (*self.ptr.as_ptr()).resource_types } + } } impl Deref for OwnedComponentInstance { @@ -666,55 +1008,56 @@ impl VMOpaqueContext { #[allow(missing_docs)] #[repr(transparent)] -pub struct InstanceFlags(*mut VMGlobalDefinition); +#[derive(Copy, Clone)] +pub struct InstanceFlags(SendSyncPtr); #[allow(missing_docs)] impl InstanceFlags { #[inline] pub unsafe fn may_leave(&self) -> bool { - *(*self.0).as_i32() & FLAG_MAY_LEAVE != 0 + *(*self.as_raw()).as_i32() & FLAG_MAY_LEAVE != 0 } #[inline] pub unsafe fn set_may_leave(&mut self, val: bool) { if val { - *(*self.0).as_i32_mut() |= FLAG_MAY_LEAVE; + *(*self.as_raw()).as_i32_mut() |= FLAG_MAY_LEAVE; } else { - *(*self.0).as_i32_mut() &= !FLAG_MAY_LEAVE; + *(*self.as_raw()).as_i32_mut() &= !FLAG_MAY_LEAVE; } } #[inline] pub unsafe fn may_enter(&self) -> bool { - *(*self.0).as_i32() & FLAG_MAY_ENTER != 0 + *(*self.as_raw()).as_i32() & FLAG_MAY_ENTER != 0 } #[inline] pub unsafe fn set_may_enter(&mut self, val: bool) { if val { - *(*self.0).as_i32_mut() |= FLAG_MAY_ENTER; + *(*self.as_raw()).as_i32_mut() |= FLAG_MAY_ENTER; } else { - *(*self.0).as_i32_mut() &= !FLAG_MAY_ENTER; + *(*self.as_raw()).as_i32_mut() &= !FLAG_MAY_ENTER; } } #[inline] pub unsafe fn needs_post_return(&self) -> bool { - *(*self.0).as_i32() & FLAG_NEEDS_POST_RETURN != 0 + *(*self.as_raw()).as_i32() & FLAG_NEEDS_POST_RETURN != 0 } #[inline] pub unsafe fn set_needs_post_return(&mut self, val: bool) { if val { - *(*self.0).as_i32_mut() |= FLAG_NEEDS_POST_RETURN; + *(*self.as_raw()).as_i32_mut() |= FLAG_NEEDS_POST_RETURN; } else { - *(*self.0).as_i32_mut() &= !FLAG_NEEDS_POST_RETURN; + *(*self.as_raw()).as_i32_mut() &= !FLAG_NEEDS_POST_RETURN; } } #[inline] pub fn as_raw(&self) -> *mut VMGlobalDefinition { - self.0 + self.0.as_ptr() } } diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs new file mode 100644 index 000000000000..378fe1e24926 --- /dev/null +++ b/crates/runtime/src/component/resources.rs @@ -0,0 +1,327 @@ +//! Implementation of the canonical-ABI related intrinsics for resources in the +//! component model. +//! +//! This module contains all the relevant gory details of the details of the +//! component model related to lifting and lowering resources. For example +//! intrinsics like `resource.new` will bottom out in calling this file, and +//! this is where resource tables are actually defined and modified. +//! +//! The main types in this file are: +//! +//! * `ResourceTables` - the "here's everything" context which is required to +//! perform canonical ABI operations. +//! +//! * `ResourceTable` - an individual instance of a table of resources, +//! basically "just a slab" though. +//! +//! * `CallContexts` - store-local information about active calls and borrows +//! and runtime state tracking that to ensure that everything is handled +//! correctly. +//! +//! Individual operations are exposed through methods on `ResourceTables` for +//! lifting/lowering/etc. This does mean though that some other fiddly bits +//! about ABI details can be found in lifting/lowering thoughout Wasmtime, +//! namely in the `Resource` and `ResourceAny` types. + +use anyhow::{bail, Result}; +use std::mem; +use wasmtime_environ::component::TypeResourceTableIndex; +use wasmtime_environ::PrimaryMap; + +/// Contextual state necessary to perform resource-related operations. +/// +/// This state a bit odd since it has a few optional bits, but the idea is that +/// whenever this is constructed the bits required to perform operations are +/// always `Some`. For example: +/// +/// * During lifting and lowering both `tables` and `host_table` are `Some`. +/// * During wasm's own intrinsics only `tables` is `Some`. +/// * During embedder-invoked resource destruction calls only `host_table` is +/// `Some`. +/// +/// This is all packaged up into one state though to make it easier to operate +/// on and to centralize handling of the state related to resources due to how +/// critical it is for correctness. +pub struct ResourceTables<'a> { + /// Runtime state for all resources defined in a component. + /// + /// This is required whenever a `TypeResourceTableIndex` is provided as it's + /// the lookup where that happens. Not present during embedder-originating + /// operations though such as `ResourceAny::resource_drop` which won't + /// consult this table as it's only operating over the host table. + pub tables: Option<&'a mut PrimaryMap>, + + /// Runtime state for resources currently owned by the host. + /// + /// This is the single table used by the host stored within `Store`. Host + /// resources will point into this and effectively have the same semantics + /// as-if they're in-component resources. The major distinction though is + /// that this is a heterogenous table instead of only containing a single + /// type. + pub host_table: Option<&'a mut ResourceTable>, + + /// Scope information about calls actively in use to track information such + /// as borrow counts. + pub calls: &'a mut CallContexts, +} + +/// An individual slab of resources used for a single table within a component. +/// Not much fancier than a general slab data structure. +#[derive(Default)] +pub struct ResourceTable { + /// Next slot to allocate, or `self.slots.len()` if they're all full. + next: u32, + /// Runtime state of all slots. + slots: Vec, +} + +enum Slot { + /// This slot is free and points to the next free slot, forming a linked + /// list of free slots. + Free { next: u32 }, + + /// This slot contains an owned resource with the listed representation. + /// + /// The `lend_count` tracks how many times this has been lent out as a + /// `borrow` and if nonzero this can't be removed. + Own { rep: u32, lend_count: u32 }, + + /// This slot contains a `borrow` resource that's connected to the `scope` + /// provided. The `rep` is listed and dropping this borrow will decrement + /// the borrow count of the `scope`. + Borrow { rep: u32, scope: usize }, +} + +/// State related to borrows and calls within a component. +/// +/// This is created once per `Store` and updated and modified throughout the +/// lifetime of the store. This primarily tracks borrow counts and what slots +/// should be updated when calls go out of scope. +#[derive(Default)] +pub struct CallContexts { + scopes: Vec, +} + +#[derive(Default)] +struct CallContext { + lenders: Vec, + borrow_count: u32, +} + +#[derive(Copy, Clone)] +struct Lender { + ty: Option, + idx: u32, +} + +impl ResourceTables<'_> { + fn table(&mut self, ty: Option) -> &mut ResourceTable { + match ty { + None => self.host_table.as_mut().unwrap(), + Some(idx) => &mut self.tables.as_mut().unwrap()[idx], + } + } + + /// Implementation of the `resource.new` canonical intrinsic. + /// + /// Note that this is the same as `resource_lower_own`. + pub fn resource_new(&mut self, ty: Option, rep: u32) -> u32 { + self.table(ty).insert(Slot::Own { rep, lend_count: 0 }) + } + + /// Implementation of the `resource.rep` canonical intrinsic. + /// + /// This one's one of the simpler ones: "just get the rep please" + pub fn resource_rep(&mut self, ty: Option, idx: u32) -> Result { + self.table(ty).rep(idx) + } + + /// Implementation of the `resource.drop` canonical intrinsic minus the + /// actual invocation of the destructor. + /// + /// This will drop the handle at the `idx` specified, removing it from the + /// specified table. This operation can fail if: + /// + /// * The index is invalid. + /// * The index points to an `own` resource which has active borrows. + /// + /// Otherwise this will return `Some(rep)` if the destructor for `rep` needs + /// to run. If `None` is returned then that means a `borrow` handle was + /// removed and no destructor is necessary. + pub fn resource_drop( + &mut self, + ty: Option, + idx: u32, + ) -> Result> { + match self.table(ty).remove(idx)? { + Slot::Own { rep, lend_count: 0 } => Ok(Some(rep)), + Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"), + Slot::Borrow { scope, .. } => { + self.calls.scopes[scope].borrow_count -= 1; + Ok(None) + } + Slot::Free { .. } => unreachable!(), + } + } + + /// Inserts a new "own" handle into the specified table. + /// + /// This will insert the specified representation into the specified type + /// table. + /// + /// Note that this operation is infallible, and additionally that this is + /// the same as `resource_new` implementation-wise. + /// + /// This is an implementation of the canonical ABI `lower_own` function. + pub fn resource_lower_own(&mut self, ty: Option, rep: u32) -> u32 { + self.table(ty).insert(Slot::Own { rep, lend_count: 0 }) + } + + /// Attempts to remove an "own" handle from the specified table and its + /// index. + /// + /// This operation will fail if `idx` is invalid, if it's a `borrow` handle, + /// or if the own handle has currently been "lent" as a borrow. + /// + /// This is an implementation of the canonical ABI `lift_own` function. + pub fn resource_lift_own( + &mut self, + ty: Option, + idx: u32, + ) -> Result { + match self.table(ty).remove(idx)? { + Slot::Own { rep, lend_count: 0 } => Ok(rep), + Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"), + Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"), + Slot::Free { .. } => unreachable!(), + } + } + + /// Extracts the underlying resource representation by lifting a "borrow" + /// from the tables. + /// + /// This primarily employs dynamic tracking when a borrow is created from an + /// "own" handle to ensure that the "own" handle isn't dropped while the + /// borrow is active and additionally that when the current call scope + /// returns the lend operation is undone. + /// + /// This is an implementation of the canonical ABI `lift_borrow` function. + pub fn resource_lift_borrow( + &mut self, + ty: Option, + idx: u32, + ) -> Result { + match self.table(ty).get_mut(idx)? { + Slot::Own { rep, lend_count } => { + // The decrement to this count happens in `exit_call`. + *lend_count = lend_count.checked_add(1).unwrap(); + let rep = *rep; + let scope = self.calls.scopes.last_mut().unwrap(); + scope.lenders.push(Lender { ty, idx }); + Ok(rep) + } + Slot::Borrow { rep, .. } => Ok(*rep), + Slot::Free { .. } => unreachable!(), + } + } + + /// Records a new `borrow` resource with the given representation within the + /// current call scope. + /// + /// This requires that a call scope is active. Additionally the number of + /// active borrows in the latest scope will be increased and must be + /// decreased through a future use of `resource_drop` before the current + /// call scope exits. + /// + /// This some of the implementation of the canonical ABI `lower_borrow` + /// function. The other half of this implementation is located on + /// `VMComponentContext` which handles the special case of avoiding borrow + /// tracking entirely. + pub fn resource_lower_borrow(&mut self, ty: Option, rep: u32) -> u32 { + let scope = self.calls.scopes.len() - 1; + let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count; + *borrow_count = borrow_count.checked_add(1).unwrap(); + self.table(ty).insert(Slot::Borrow { rep, scope }) + } + + /// Enters a new calling context, starting a fresh count of borrows and + /// such. + pub fn enter_call(&mut self) { + self.calls.scopes.push(CallContext::default()); + } + + /// Exits the previously pushed calling context. + /// + /// This requires all information to be available within this + /// `ResourceTables` and is only called during lowering/lifting operations + /// at this time. + pub fn exit_call(&mut self) -> Result<()> { + let cx = self.calls.scopes.pop().unwrap(); + if cx.borrow_count > 0 { + bail!("borrow handles still remain at the end of the call") + } + for lender in cx.lenders.iter() { + // Note the panics here which should never get triggered in theory + // due to the dynamic tracking of borrows and such employed for + // resources. + match self.table(lender.ty).get_mut(lender.idx).unwrap() { + Slot::Own { lend_count, .. } => { + *lend_count -= 1; + } + _ => unreachable!(), + } + } + Ok(()) + } +} + +impl ResourceTable { + fn next(&self) -> usize { + self.next as usize + } + + fn insert(&mut self, new: Slot) -> u32 { + let next = self.next(); + if next == self.slots.len() { + self.slots.push(Slot::Free { + next: self.next.checked_add(1).unwrap(), + }); + } + let ret = self.next; + self.next = match mem::replace(&mut self.slots[next], new) { + Slot::Free { next } => next, + _ => unreachable!(), + }; + u32::try_from(ret).unwrap() + } + + fn rep(&self, idx: u32) -> Result { + match usize::try_from(idx).ok().and_then(|i| self.slots.get(i)) { + None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), + Some(Slot::Own { rep, .. } | Slot::Borrow { rep, .. }) => Ok(*rep), + } + } + + fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> { + match usize::try_from(idx) + .ok() + .and_then(|i| self.slots.get_mut(i)) + { + None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), + Some(other) => Ok(other), + } + } + + fn remove(&mut self, idx: u32) -> Result { + match usize::try_from(idx).ok().and_then(|i| self.slots.get(i)) { + Some(Slot::Own { .. }) | Some(Slot::Borrow { .. }) => {} + _ => bail!("unknown handle index {idx}"), + }; + let ret = mem::replace( + &mut self.slots[idx as usize], + Slot::Free { next: self.next }, + ); + self.next = idx; + Ok(ret) + } +} diff --git a/crates/runtime/src/component/transcode.rs b/crates/runtime/src/component/transcode.rs index e67b87a0bc34..7aebc5bc7e22 100644 --- a/crates/runtime/src/component/transcode.rs +++ b/crates/runtime/src/component/transcode.rs @@ -1,11 +1,70 @@ //! Implementation of string transcoding required by the component model. +use crate::component::{ComponentInstance, VMComponentContext}; use anyhow::{anyhow, Result}; use std::cell::Cell; use std::slice; +use wasmtime_environ::component::TypeResourceTableIndex; const UTF16_TAG: usize = 1 << 31; +#[repr(C)] // this is read by Cranelift code so it's layout must be as-written +pub struct VMComponentLibcalls { + builtins: VMComponentBuiltins, + transcoders: VMBuiltinTranscodeArray, +} + +impl VMComponentLibcalls { + pub const INIT: VMComponentLibcalls = VMComponentLibcalls { + builtins: VMComponentBuiltins::INIT, + transcoders: VMBuiltinTranscodeArray::INIT, + }; +} + +macro_rules! signature { + (@ty size) => (usize); + (@ty size_pair) => (usize); + (@ty ptr_u8) => (*mut u8); + (@ty ptr_u16) => (*mut u16); + (@ty ptr_size) => (*mut usize); + (@ty u32) => (u32); + (@ty u64) => (u64); + (@ty vmctx) => (*mut VMComponentContext); + + (@retptr size_pair) => (*mut usize); + (@retptr $other:ident) => (()); +} + +/// Defines a `VMComponentBuiltins` structure which contains any builtins such +/// as resource-related intrinsics. +macro_rules! define_builtins { + ( + $( + $( #[$attr:meta] )* + $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?; + )* + ) => { + /// An array that stores addresses of builtin functions. We translate code + /// to use indirect calls. This way, we don't have to patch the code. + #[repr(C)] + pub struct VMComponentBuiltins { + $( + $name: unsafe extern "C" fn( + $(signature!(@ty $param),)* + ) $( -> signature!(@ty $result))?, + )* + } + + impl VMComponentBuiltins { + pub const INIT: VMComponentBuiltins = VMComponentBuiltins { + $($name: trampolines::$name,)* + }; + } + }; +} + +wasmtime_environ::foreach_builtin_component_function!(define_builtins); + /// Macro to define the `VMBuiltinTranscodeArray` type which contains all of the /// function pointers to the actual transcoder functions. This structure is read /// by Cranelift-generated code, hence the `repr(C)`. @@ -28,8 +87,8 @@ macro_rules! define_transcoders { pub struct VMBuiltinTranscodeArray { $( $name: unsafe extern "C" fn( - $(define_transcoders!(@ty $param),)* - ) $( -> define_transcoders!(@ty $result))?, + $(signature!(@ty $param),)* + ) $( -> signature!(@ty $result))?, )* } @@ -39,11 +98,6 @@ macro_rules! define_transcoders { }; } }; - - (@ty size) => (usize); - (@ty ptr_u8) => (*mut u8); - (@ty ptr_u16) => (*mut u16); - (@ty ptr_size) => (*mut usize); } wasmtime_environ::foreach_transcoder!(define_transcoders); @@ -54,7 +108,9 @@ wasmtime_environ::foreach_transcoder!(define_transcoders); /// implementation following this submodule. #[allow(improper_ctypes_definitions)] mod trampolines { - macro_rules! transcoders { + use super::VMComponentContext; + + macro_rules! shims { ( $( $( #[$attr:meta] )* @@ -63,9 +119,9 @@ mod trampolines { ) => ( $( pub unsafe extern "C" fn $name( - $($pname : define_transcoders!(@ty $param),)* - ) $( -> define_transcoders!(@ty $result))? { - $(transcoders!(@validate_param $pname $param);)* + $($pname : signature!(@ty $param),)* + ) $( -> signature!(@ty $result))? { + $(shims!(@validate_param $pname $param);)* // Always catch panics to avoid trying to unwind from Rust // into Cranelift-generated code which would lead to a Bad @@ -74,10 +130,10 @@ mod trampolines { // Additionally assume that every function below returns a // `Result` where errors turn into traps. let result = std::panic::catch_unwind(|| { - transcoders!(@invoke $name() $($pname)*) + shims!(@invoke $name() $($pname)*) }); match result { - Ok(Ok(ret)) => transcoders!(@convert_ret ret $($pname: $param)*), + Ok(Ok(ret)) => shims!(@convert_ret ret $($pname: $param)*), Ok(Err(err)) => crate::traphandlers::raise_trap( crate::traphandlers::TrapReason::User { error: err, @@ -99,7 +155,7 @@ mod trampolines { a }); (@convert_ret $ret:ident $name:ident: $ty:ident $($rest:tt)*) => ( - transcoders!(@convert_ret $ret $($rest)*) + shims!(@convert_ret $ret $($rest)*) ); (@validate_param $arg:ident ptr_u16) => ({ @@ -116,16 +172,17 @@ mod trampolines { // ignore `ret2`-named arguments (@invoke $m:ident ($($args:tt)*) ret2 $($rest:tt)*) => ( - transcoders!(@invoke $m ($($args)*) $($rest)*) + shims!(@invoke $m ($($args)*) $($rest)*) ); // move all other arguments into the `$args` list (@invoke $m:ident ($($args:tt)*) $param:ident $($rest:tt)*) => ( - transcoders!(@invoke $m ($($args)* $param,) $($rest)*) + shims!(@invoke $m ($($args)* $param,) $($rest)*) ); } - wasmtime_environ::foreach_transcoder!(transcoders); + wasmtime_environ::foreach_builtin_component_function!(shims); + wasmtime_environ::foreach_transcoder!(shims); } /// This property should already be guaranteed by construction in the component @@ -459,3 +516,25 @@ fn inflate_latin1_bytes(dst: &mut [u16], latin1_bytes_so_far: usize) -> &mut [u1 return rest; } + +unsafe fn resource_new32(vmctx: *mut VMComponentContext, resource: u32, rep: u32) -> Result { + let resource = TypeResourceTableIndex::from_u32(resource); + Ok(ComponentInstance::from_vmctx(vmctx, |instance| { + instance.resource_new32(resource, rep) + })) +} + +unsafe fn resource_rep32(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result { + let resource = TypeResourceTableIndex::from_u32(resource); + ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_rep32(resource, idx)) +} + +unsafe fn resource_drop(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result { + let resource = TypeResourceTableIndex::from_u32(resource); + ComponentInstance::from_vmctx(vmctx, |instance| { + Ok(match instance.resource_drop(resource, idx)? { + Some(rep) => (u64::from(rep) << 1) | 1, + None => 0, + }) + }) +} diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index e23851799348..23896612af6e 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -152,6 +152,10 @@ pub unsafe trait Store { /// number. Cannot fail; cooperative epoch-based yielding is /// completely semantically transparent. Returns the new deadline. fn new_epoch(&mut self) -> Result; + + /// Metadata required for resources for the component model. + #[cfg(feature = "component-model")] + fn component_calls(&mut self) -> &mut component::CallContexts; } /// Functionality required by this crate for a particular module. This diff --git a/crates/runtime/src/send_sync_ptr.rs b/crates/runtime/src/send_sync_ptr.rs index a8a129645de5..ce9d8b422ed5 100644 --- a/crates/runtime/src/send_sync_ptr.rs +++ b/crates/runtime/src/send_sync_ptr.rs @@ -67,3 +67,11 @@ impl Clone for SendSyncPtr { } impl Copy for SendSyncPtr {} + +impl PartialEq for SendSyncPtr { + fn eq(&self, other: &SendSyncPtr) -> bool { + self.0 == other.0 + } +} + +impl Eq for SendSyncPtr {} diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index 1817de872406..24fadd4cc996 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -40,13 +40,15 @@ type CompileInput<'a> = /// Two `u32`s to align with `cranelift_codegen::ir::UserExternalName`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] struct CompileKey { - // [ kind:i3 module:i29 ] + // [ kind:i4 module:i28 ] namespace: u32, index: u32, } impl CompileKey { - const KIND_MASK: u32 = 0b111 << 29; + const KIND_BITS: u32 = 4; + const KIND_OFFSET: u32 = 32 - Self::KIND_BITS; + const KIND_MASK: u32 = ((1 << Self::KIND_BITS) - 1) << Self::KIND_OFFSET; fn kind(&self) -> u32 { self.namespace & Self::KIND_MASK @@ -56,10 +58,16 @@ impl CompileKey { StaticModuleIndex::from_u32(self.namespace & !Self::KIND_MASK) } - const WASM_FUNCTION_KIND: u32 = 0b000 << 29; - const ARRAY_TO_WASM_TRAMPOLINE_KIND: u32 = 0b001 << 29; - const NATIVE_TO_WASM_TRAMPOLINE_KIND: u32 = 0b010 << 29; - const WASM_TO_NATIVE_TRAMPOLINE_KIND: u32 = 0b011 << 29; + const WASM_FUNCTION_KIND: u32 = Self::new_kind(0); + const ARRAY_TO_WASM_TRAMPOLINE_KIND: u32 = Self::new_kind(1); + const NATIVE_TO_WASM_TRAMPOLINE_KIND: u32 = Self::new_kind(2); + const WASM_TO_NATIVE_TRAMPOLINE_KIND: u32 = Self::new_kind(3); + + const fn new_kind(kind: u32) -> u32 { + assert!(kind < (1 << Self::KIND_BITS)); + kind << Self::KIND_OFFSET + } + // NB: more kinds in the other `impl` block. fn wasm_function(module: StaticModuleIndex, index: DefinedFuncIndex) -> Self { @@ -96,9 +104,13 @@ impl CompileKey { #[cfg(feature = "component-model")] impl CompileKey { - const LOWERING_KIND: u32 = 0b100 << 29; - const ALWAYS_TRAP_KIND: u32 = 0b101 << 29; - const TRANSCODER_KIND: u32 = 0b110 << 29; + const LOWERING_KIND: u32 = Self::new_kind(4); + const ALWAYS_TRAP_KIND: u32 = Self::new_kind(5); + const TRANSCODER_KIND: u32 = Self::new_kind(6); + const RESOURCE_NEW_KIND: u32 = Self::new_kind(7); + const RESOURCE_REP_KIND: u32 = Self::new_kind(8); + const RESOURCE_DROP_KIND: u32 = Self::new_kind(9); + const RESOURCE_DROP_WASM_TO_NATIVE_KIND: u32 = Self::new_kind(10); fn lowering(index: wasmtime_environ::component::LoweredIndex) -> Self { Self { @@ -120,6 +132,34 @@ impl CompileKey { index: index.as_u32(), } } + + fn resource_new(index: wasmtime_environ::component::RuntimeResourceNewIndex) -> Self { + Self { + namespace: Self::RESOURCE_NEW_KIND, + index: index.as_u32(), + } + } + + fn resource_rep(index: wasmtime_environ::component::RuntimeResourceRepIndex) -> Self { + Self { + namespace: Self::RESOURCE_REP_KIND, + index: index.as_u32(), + } + } + + fn resource_drop(index: wasmtime_environ::component::RuntimeResourceDropIndex) -> Self { + Self { + namespace: Self::RESOURCE_DROP_KIND, + index: index.as_u32(), + } + } + + fn resource_drop_wasm_to_native_trampoline() -> Self { + Self { + namespace: Self::RESOURCE_DROP_WASM_TO_NATIVE_KIND, + index: 0, + } + } } #[derive(Clone, Copy)] @@ -162,34 +202,31 @@ struct CompileOutput { } /// The collection of things we need to compile for a Wasm module or component. +#[derive(Default)] pub struct CompileInputs<'a> { inputs: Vec>, } -fn push_input<'a, 'data>( - inputs: &mut Vec>, - f: impl FnOnce(&Tunables, &dyn Compiler) -> Result + Send + 'a, -) { - inputs.push(Box::new(f) as _); -} - impl<'a> CompileInputs<'a> { + fn push_input( + &mut self, + f: impl FnOnce(&Tunables, &dyn Compiler) -> Result + Send + 'a, + ) { + self.inputs.push(Box::new(f)); + } + /// Create the `CompileInputs` for a core Wasm module. pub fn for_module( types: &'a ModuleTypes, translation: &'a ModuleTranslation<'a>, functions: PrimaryMap>, ) -> Self { - let mut inputs = vec![]; + let mut ret = Self::default(); let module_index = StaticModuleIndex::from_u32(0); - Self::collect_inputs_in_translations( - types, - [(module_index, translation, functions)], - &mut inputs, - ); + ret.collect_inputs_in_translations(types, [(module_index, translation, functions)]); - CompileInputs { inputs } + ret } /// Create a `CompileInputs` for a component. @@ -205,18 +242,14 @@ impl<'a> CompileInputs<'a> { ), >, ) -> Self { - let mut inputs = vec![]; + let mut ret = CompileInputs::default(); - Self::collect_inputs_in_translations( - types.module_types(), - module_translations, - &mut inputs, - ); + ret.collect_inputs_in_translations(types.module_types(), module_translations); for init in &component.initializers { match init { wasmtime_environ::component::GlobalInitializer::AlwaysTrap(always_trap) => { - push_input(&mut inputs, move |_tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { key: CompileKey::always_trap(always_trap.index), symbol: always_trap.symbol_name(), @@ -229,7 +262,7 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::Transcoder(transcoder) => { - push_input(&mut inputs, move |_tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { key: CompileKey::transcoder(transcoder.index), symbol: transcoder.symbol_name(), @@ -242,7 +275,7 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::LowerImport(lower_import) => { - push_input(&mut inputs, move |_tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { key: CompileKey::lowering(lower_import.index), symbol: lower_import.symbol_name(), @@ -254,7 +287,49 @@ impl<'a> CompileInputs<'a> { }) }); } - wasmtime_environ::component::GlobalInitializer::InstantiateModule(_) + + wasmtime_environ::component::GlobalInitializer::ResourceNew(r) => { + ret.push_input(move |_tunables, compiler| { + Ok(CompileOutput { + key: CompileKey::resource_new(r.index), + symbol: r.symbol_name(), + function: compiler + .component_compiler() + .compile_resource_new(component, r, types)? + .into(), + info: None, + }) + }); + } + wasmtime_environ::component::GlobalInitializer::ResourceRep(r) => { + ret.push_input(move |_tunables, compiler| { + Ok(CompileOutput { + key: CompileKey::resource_rep(r.index), + symbol: r.symbol_name(), + function: compiler + .component_compiler() + .compile_resource_rep(component, r, types)? + .into(), + info: None, + }) + }); + } + wasmtime_environ::component::GlobalInitializer::ResourceDrop(r) => { + ret.push_input(move |_tunables, compiler| { + Ok(CompileOutput { + key: CompileKey::resource_drop(r.index), + symbol: r.symbol_name(), + function: compiler + .component_compiler() + .compile_resource_drop(component, r, types)? + .into(), + info: None, + }) + }); + } + + wasmtime_environ::component::GlobalInitializer::Resource(_) + | wasmtime_environ::component::GlobalInitializer::InstantiateModule(_) | wasmtime_environ::component::GlobalInitializer::ExtractMemory(_) | wasmtime_environ::component::GlobalInitializer::ExtractRealloc(_) | wasmtime_environ::component::GlobalInitializer::ExtractPostReturn(_) => { @@ -263,10 +338,37 @@ impl<'a> CompileInputs<'a> { } } - CompileInputs { inputs } + // If a host-defined resource is destroyed from wasm then a + // wasm-to-native trampoline will be required when creating the + // `VMFuncRef` for the host resource's destructor. This snippet is an + // overeager approximation of this where if a component has any + // resources and the signature of `resource.drop` is mentioned anywhere + // in the component then assume this situation is going to happen. + // + // To handle this a wasm-to-native trampoline for the signature is + // generated here. Note that this may duplicate one wasm-to-native + // trampoline as it may already exist for the signature elsewhere in the + // file. Doing this here though helps simplify this compilation process + // so it's an accepted overhead for now. + if component.num_resources > 0 { + if let Some(sig) = types.find_resource_drop_signature() { + ret.push_input(move |_tunables, compiler| { + let trampoline = compiler.compile_wasm_to_native_trampoline(&types[sig])?; + Ok(CompileOutput { + key: CompileKey::resource_drop_wasm_to_native_trampoline(), + symbol: "resource_drop_trampoline".to_string(), + function: CompiledFunction::Function(trampoline), + info: None, + }) + }); + } + } + + ret } fn collect_inputs_in_translations( + &mut self, types: &'a ModuleTypes, translations: impl IntoIterator< Item = ( @@ -275,13 +377,12 @@ impl<'a> CompileInputs<'a> { PrimaryMap>, ), >, - inputs: &mut Vec>, ) { - let mut sigs = BTreeMap::new(); + let mut sigs = BTreeSet::new(); for (module, translation, functions) in translations { for (def_func_index, func_body) in functions { - push_input(inputs, move |tunables, compiler| { + self.push_input(move |tunables, compiler| { let func_index = translation.module.func_index(def_func_index); let (info, function) = compiler.compile_function( translation, @@ -304,7 +405,7 @@ impl<'a> CompileInputs<'a> { let func_index = translation.module.func_index(def_func_index); if translation.module.functions[func_index].is_escaping() { - push_input(inputs, move |_tunables, compiler| { + self.push_input(move |_tunables, compiler| { let func_index = translation.module.func_index(def_func_index); let trampoline = compiler.compile_array_to_wasm_trampoline( translation, @@ -323,7 +424,7 @@ impl<'a> CompileInputs<'a> { }) }); - push_input(inputs, move |_tunables, compiler| { + self.push_input(move |_tunables, compiler| { let func_index = translation.module.func_index(def_func_index); let trampoline = compiler.compile_native_to_wasm_trampoline( translation, @@ -345,15 +446,14 @@ impl<'a> CompileInputs<'a> { } sigs.extend(translation.module.types.iter().map(|(_, ty)| match ty { - ModuleType::Function(ty) => (*ty, translation), + ModuleType::Function(ty) => *ty, })); } - for (signature, translation) in sigs { - push_input(inputs, move |_tunables, compiler| { + for signature in sigs { + self.push_input(move |_tunables, compiler| { let wasm_func_ty = &types[signature]; - let trampoline = - compiler.compile_wasm_to_native_trampoline(translation, wasm_func_ty)?; + let trampoline = compiler.compile_wasm_to_native_trampoline(wasm_func_ty)?; Ok(CompileOutput { key: CompileKey::wasm_to_native_trampoline(signature), symbol: format!( @@ -669,6 +769,36 @@ impl FunctionIndices { .into_iter() .map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1)) .collect(); + artifacts.resource_new = self + .indices + .remove(&CompileKey::RESOURCE_NEW_KIND) + .unwrap_or_default() + .into_iter() + .map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1)) + .collect(); + artifacts.resource_rep = self + .indices + .remove(&CompileKey::RESOURCE_REP_KIND) + .unwrap_or_default() + .into_iter() + .map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1)) + .collect(); + artifacts.resource_drop = self + .indices + .remove(&CompileKey::RESOURCE_DROP_KIND) + .unwrap_or_default() + .into_iter() + .map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1)) + .collect(); + let map = self + .indices + .remove(&CompileKey::RESOURCE_DROP_WASM_TO_NATIVE_KIND) + .unwrap_or_default(); + assert!(map.len() <= 1); + artifacts.resource_drop_wasm_to_native_trampoline = map + .into_iter() + .next() + .map(|(_id, x)| symbol_ids_and_locs[x.unwrap_function()].1); } debug_assert!( @@ -700,6 +830,23 @@ pub struct Artifacts { wasmtime_environ::component::RuntimeTranscoderIndex, wasmtime_environ::component::AllCallFunc, >, + #[cfg(feature = "component-model")] + pub resource_new: PrimaryMap< + wasmtime_environ::component::RuntimeResourceNewIndex, + wasmtime_environ::component::AllCallFunc, + >, + #[cfg(feature = "component-model")] + pub resource_rep: PrimaryMap< + wasmtime_environ::component::RuntimeResourceRepIndex, + wasmtime_environ::component::AllCallFunc, + >, + #[cfg(feature = "component-model")] + pub resource_drop: PrimaryMap< + wasmtime_environ::component::RuntimeResourceDropIndex, + wasmtime_environ::component::AllCallFunc, + >, + #[cfg(feature = "component-model")] + pub resource_drop_wasm_to_native_trampoline: Option, } impl Artifacts { @@ -712,6 +859,9 @@ impl Artifacts { assert!(self.lowerings.is_empty()); assert!(self.always_traps.is_empty()); assert!(self.transcoders.is_empty()); + assert!(self.resource_new.is_empty()); + assert!(self.resource_rep.is_empty()); + assert!(self.resource_drop.is_empty()); } self.modules.into_iter().next().unwrap().1 } diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index afb0ce481888..7665ac8614a1 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -9,14 +9,16 @@ use std::path::Path; use std::ptr::NonNull; use std::sync::Arc; use wasmtime_environ::component::{ - AllCallFunc, ComponentTypes, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeTranscoderIndex, - StaticModuleIndex, Translator, + AllCallFunc, ComponentTypes, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeResourceDropIndex, + RuntimeResourceNewIndex, RuntimeResourceRepIndex, RuntimeTranscoderIndex, StaticModuleIndex, + Translator, }; use wasmtime_environ::{FunctionLoc, ObjectKind, PrimaryMap, ScopeVec}; use wasmtime_jit::{CodeMemory, CompiledModuleInfo}; use wasmtime_runtime::component::ComponentRuntimeInfo; use wasmtime_runtime::{ - MmapVec, VMArrayCallFunction, VMFunctionBody, VMNativeCallFunction, VMWasmCallFunction, + MmapVec, VMArrayCallFunction, VMFuncRef, VMFunctionBody, VMNativeCallFunction, + VMWasmCallFunction, }; /// A compiled WebAssembly Component. @@ -73,6 +75,20 @@ struct CompiledComponentInfo { /// Where all the cranelift-generated transcode functions are located in the /// compiled image of this component. transcoders: PrimaryMap>, + + /// Locations of cranelift-generated `resource.new` functions are located + /// within the component. + resource_new: PrimaryMap>, + + /// Same as `resource_new`, but for `resource.rep` intrinsics. + resource_rep: PrimaryMap>, + + /// Same as `resource_new`, but for `resource.drop` intrinsics. + resource_drop: PrimaryMap>, + + /// The location of the wasm-to-native trampoline for the `resource.drop` + /// intrinsic. + resource_drop_wasm_to_native_trampoline: Option, } pub(crate) struct AllCallFuncPointers { @@ -225,6 +241,11 @@ impl Component { always_trap: compilation_artifacts.always_traps, lowerings: compilation_artifacts.lowerings, transcoders: compilation_artifacts.transcoders, + resource_new: compilation_artifacts.resource_new, + resource_rep: compilation_artifacts.resource_rep, + resource_drop: compilation_artifacts.resource_drop, + resource_drop_wasm_to_native_trampoline: compilation_artifacts + .resource_drop_wasm_to_native_trampoline, }; let artifacts = ComponentArtifacts { info, @@ -303,12 +324,12 @@ impl Component { self.inner.code.code_memory().text() } - pub(crate) fn lowering_ptrs(&self, index: LoweredIndex) -> AllCallFuncPointers { + fn all_call_func_ptrs(&self, func: &AllCallFunc) -> AllCallFuncPointers { let AllCallFunc { wasm_call, array_call, native_call, - } = &self.inner.info.lowerings[index]; + } = func; AllCallFuncPointers { wasm_call: self.func(wasm_call).cast(), array_call: unsafe { @@ -320,38 +341,31 @@ impl Component { } } + pub(crate) fn lowering_ptrs(&self, index: LoweredIndex) -> AllCallFuncPointers { + self.all_call_func_ptrs(&self.inner.info.lowerings[index]) + } + pub(crate) fn always_trap_ptrs(&self, index: RuntimeAlwaysTrapIndex) -> AllCallFuncPointers { - let AllCallFunc { - wasm_call, - array_call, - native_call, - } = &self.inner.info.always_trap[index]; - AllCallFuncPointers { - wasm_call: self.func(wasm_call).cast(), - array_call: unsafe { - mem::transmute::, VMArrayCallFunction>( - self.func(array_call), - ) - }, - native_call: self.func(native_call).cast(), - } + self.all_call_func_ptrs(&self.inner.info.always_trap[index]) } pub(crate) fn transcoder_ptrs(&self, index: RuntimeTranscoderIndex) -> AllCallFuncPointers { - let AllCallFunc { - wasm_call, - array_call, - native_call, - } = &self.inner.info.transcoders[index]; - AllCallFuncPointers { - wasm_call: self.func(wasm_call).cast(), - array_call: unsafe { - mem::transmute::, VMArrayCallFunction>( - self.func(array_call), - ) - }, - native_call: self.func(native_call).cast(), - } + self.all_call_func_ptrs(&self.inner.info.transcoders[index]) + } + + pub(crate) fn resource_new_ptrs(&self, index: RuntimeResourceNewIndex) -> AllCallFuncPointers { + self.all_call_func_ptrs(&self.inner.info.resource_new[index]) + } + + pub(crate) fn resource_rep_ptrs(&self, index: RuntimeResourceRepIndex) -> AllCallFuncPointers { + self.all_call_func_ptrs(&self.inner.info.resource_rep[index]) + } + + pub(crate) fn resource_drop_ptrs( + &self, + index: RuntimeResourceDropIndex, + ) -> AllCallFuncPointers { + self.all_call_func_ptrs(&self.inner.info.resource_drop[index]) } fn func(&self, loc: &FunctionLoc) -> NonNull { @@ -379,6 +393,31 @@ impl Component { pub(crate) fn runtime_info(&self) -> Arc { self.inner.clone() } + + /// Creates a new `VMFuncRef` with all fields filled out for the destructor + /// specified. + /// + /// The `dtor`'s own `VMFuncRef` won't have `wasm_call` filled out but this + /// component may have `resource_drop_wasm_to_native_trampoline` filled out + /// if necessary in which case it's filled in here. + pub(crate) fn resource_drop_func_ref(&self, dtor: &crate::func::HostFunc) -> VMFuncRef { + // Host functions never have their `wasm_call` filled in at this time. + assert!(dtor.func_ref().wasm_call.is_none()); + + // Note that if `resource_drop_wasm_to_native_trampoline` is not present + // then this can't be called by the component, so it's ok to leave it + // blank. + let wasm_call = self + .inner + .info + .resource_drop_wasm_to_native_trampoline + .as_ref() + .map(|i| self.func(i).cast()); + VMFuncRef { + wasm_call, + ..*dtor.func_ref() + } + } } impl ComponentRuntimeInfo for ComponentInner { diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index a2258f0ae644..9a10b3d0ad91 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -12,6 +12,7 @@ use wasmtime_environ::component::{ CanonicalOptions, ComponentTypes, CoreDef, InterfaceType, RuntimeComponentInstanceIndex, TypeFuncIndex, TypeTuple, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, }; +use wasmtime_runtime::component::ResourceTables; use wasmtime_runtime::{Export, ExportFunction}; /// A helper macro to safely map `MaybeUninit` to `MaybeUninit` where `U` @@ -218,32 +219,40 @@ impl Func { Params: ComponentNamedList + Lower, Return: ComponentNamedList + Lift, { - self._typed(store.as_context().0) + self._typed(store.as_context().0, None) } pub(crate) fn _typed( &self, store: &StoreOpaque, + instance: Option<&InstanceData>, ) -> Result> where Params: ComponentNamedList + Lower, Return: ComponentNamedList + Lift, { - self.typecheck::(store)?; + self.typecheck::(store, instance)?; unsafe { Ok(TypedFunc::new_unchecked(*self)) } } - fn typecheck(&self, store: &StoreOpaque) -> Result<()> + fn typecheck( + &self, + store: &StoreOpaque, + instance: Option<&InstanceData>, + ) -> Result<()> where Params: ComponentNamedList + Lower, Return: ComponentNamedList + Lift, { let data = &store[self.0]; - let ty = &data.types[data.ty]; + let cx = instance + .unwrap_or_else(|| &store[data.instance.0].as_ref().unwrap()) + .ty(); + let ty = &cx.types[data.ty]; - Params::typecheck(&InterfaceType::Tuple(ty.params), &data.types) + Params::typecheck(&InterfaceType::Tuple(ty.params), &cx) .context("type mismatch with parameters")?; - Return::typecheck(&InterfaceType::Tuple(ty.results), &data.types) + Return::typecheck(&InterfaceType::Tuple(ty.results), &cx) .context("type mismatch with results")?; Ok(()) @@ -251,21 +260,25 @@ impl Func { /// Get the parameter types for this function. pub fn params(&self, store: impl AsContext) -> Box<[Type]> { - let data = &store.as_context()[self.0]; + let store = store.as_context(); + let data = &store[self.0]; + let instance = store[data.instance.0].as_ref().unwrap(); data.types[data.types[data.ty].params] .types .iter() - .map(|ty| Type::from(ty, &data.types)) + .map(|ty| Type::from(ty, &instance.ty())) .collect() } /// Get the result types for this function. pub fn results(&self, store: impl AsContext) -> Box<[Type]> { - let data = &store.as_context()[self.0]; + let store = store.as_context(); + let data = &store[self.0]; + let instance = store[data.instance.0].as_ref().unwrap(); data.types[data.types[data.ty].results] .types .iter() - .map(|ty| Type::from(ty, &data.types)) + .map(|ty| Type::from(ty, &instance.ty())) .collect() } @@ -410,7 +423,7 @@ impl Func { InterfaceType, &mut MaybeUninit, ) -> Result<()>, - lift: impl FnOnce(&LiftContext<'_>, InterfaceType, &LowerReturn) -> Result, + lift: impl FnOnce(&mut LiftContext<'_>, InterfaceType, &LowerReturn) -> Result, ) -> Result where LowerParams: Copy, @@ -455,18 +468,17 @@ impl Func { // never be entered again. The only time this flag is set to `true` // again is after post-return logic has completed successfully. if !flags.may_enter() { - bail!("cannot reenter component instance"); + bail!(crate::Trap::CannotEnterComponent); } flags.set_may_enter(false); debug_assert!(flags.may_leave()); flags.set_may_leave(false); + let instance_ptr = instance.instance_ptr(); + let mut cx = LowerContext::new(store.as_context_mut(), &options, &types, instance_ptr); + cx.enter_call(); let result = lower( - &mut LowerContext { - store: store.as_context_mut(), - options: &options, - types: &types, - }, + &mut cx, params, InterfaceType::Tuple(types[ty].params), map_maybe_uninit!(space.params), @@ -507,11 +519,7 @@ impl Func { // later get used in post-return. flags.set_needs_post_return(true); let val = lift( - &LiftContext { - store: store.0, - options: &options, - types: &types, - }, + &mut LiftContext::new(store.0, &options, &types, instance_ptr), InterfaceType::Tuple(types[ty].results), ret, )?; @@ -595,10 +603,11 @@ impl Func { let post_return = data.post_return; let component_instance = data.component_instance; let post_return_arg = data.post_return_arg.take(); - let instance = store.0[instance.0].as_ref().unwrap().instance(); - let mut flags = instance.instance_flags(component_instance); + let instance = store.0[instance.0].as_ref().unwrap().instance_ptr(); unsafe { + let mut flags = (*instance).instance_flags(component_instance); + // First assert that the instance is in a "needs post return" state. // This will ensure that the previous action on the instance was a // function call above. This flag is only set after a component @@ -648,6 +657,14 @@ impl Func { // enter" flag is set to `true` again here which enables further use // of the component. flags.set_may_enter(true); + + let (calls, host_table) = store.0.component_calls_and_host_table(); + ResourceTables { + calls, + host_table: Some(host_table), + tables: Some((*instance).component_resource_tables()), + } + .exit_call()?; } Ok(()) } @@ -673,7 +690,7 @@ impl Func { } fn load_results( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, results_ty: &TypeTuple, results: &mut [Val], src: &mut std::slice::Iter<'_, ValRaw>, diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs index 12fb60e7f604..a048275b9090 100644 --- a/crates/wasmtime/src/component/func/host.rs +++ b/crates/wasmtime/src/component/func/host.rs @@ -1,4 +1,5 @@ use crate::component::func::{LiftContext, LowerContext, Options}; +use crate::component::matching::InstanceType; use crate::component::storage::slice_to_storage_mut; use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Type, Val}; use crate::{AsContextMut, StoreContextMut, ValRaw}; @@ -19,7 +20,7 @@ use wasmtime_runtime::{VMFuncRef, VMMemoryDefinition, VMOpaqueContext}; pub struct HostFunc { entrypoint: VMLoweringCallee, - typecheck: Box) -> Result<()>) + Send + Sync>, + typecheck: Box) -> Result<()>) + Send + Sync>, func: Box, } @@ -84,7 +85,7 @@ impl HostFunc { let types = types.clone(); move |expected_index, expected_types| { - if index == expected_index && Arc::ptr_eq(&types, expected_types) { + if index == expected_index && std::ptr::eq(&*types, &**expected_types.types) { Ok(()) } else { Err(anyhow!("function type mismatch")) @@ -95,7 +96,7 @@ impl HostFunc { }) } - pub fn typecheck(&self, ty: TypeFuncIndex, types: &Arc) -> Result<()> { + pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> { (self.typecheck)(ty, types) } @@ -108,12 +109,12 @@ impl HostFunc { } } -fn typecheck(ty: TypeFuncIndex, types: &Arc) -> Result<()> +fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> where P: ComponentNamedList + Lift, R: ComponentNamedList + Lower, { - let ty = &types[ty]; + let ty = &types.types[ty]; P::typecheck(&InterfaceType::Tuple(ty.params), types) .context("type mismatch with parameters")?; R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?; @@ -219,28 +220,18 @@ where Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref()) } }; - let params = storage.lift_params( - &LiftContext { - store: cx.0, - options: &options, - types, - }, - param_tys, - )?; + let mut lift = LiftContext::new(cx.0, &options, types, instance); + lift.enter_call(); + let params = storage.lift_params(&mut lift, param_tys)?; let ret = closure(cx.as_context_mut(), params)?; flags.set_may_leave(false); - storage.lower_results( - &mut LowerContext { - store: cx, - options: &options, - types, - }, - result_tys, - ret, - )?; + let mut lower = LowerContext::new(cx, &options, types, instance); + storage.lower_results(&mut lower, result_tys, ret)?; flags.set_may_leave(true); + lower.exit_call()?; + return Ok(()); enum Storage<'a, P: ComponentType, R: ComponentType> { @@ -255,7 +246,7 @@ where P: ComponentType + Lift, R: ComponentType + Lower, { - unsafe fn lift_params(&self, cx: &LiftContext<'_>, ty: InterfaceType) -> Result

{ + unsafe fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result

{ match self { Storage::Direct(storage) => P::lift(cx, ty, &storage.assume_init_ref().args), Storage::ResultsIndirect(storage) => P::lift(cx, ty, &storage.args), @@ -355,11 +346,8 @@ where let func_ty = &types[ty]; let param_tys = &types[func_ty.params]; let result_tys = &types[func_ty.results]; - let cx = LiftContext { - store: store.0, - options: &options, - types, - }; + let mut cx = LiftContext::new(store.0, &options, types, instance); + cx.enter_call(); if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) { // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable let mut iter = @@ -367,7 +355,7 @@ where args = param_tys .types .iter() - .map(|ty| Val::lift(&cx, *ty, &mut iter)) + .map(|ty| Val::lift(&mut cx, *ty, &mut iter)) .collect::>>()?; ret_index = param_count; assert!(iter.next().is_none()); @@ -380,11 +368,8 @@ where .map(|ty| { let abi = types.canonical_abi(ty); let size = usize::try_from(abi.size32).unwrap(); - Val::load( - &cx, - *ty, - &cx.memory()[abi.next_field32_size(&mut offset)..][..size], - ) + let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size]; + Val::load(&mut cx, *ty, memory) }) .collect::>>()?; ret_index = 1; @@ -396,15 +381,12 @@ where } closure(store.as_context_mut(), &args, &mut result_vals)?; flags.set_may_leave(false); + + let mut cx = LowerContext::new(store, &options, types, instance); + let instance = cx.instance_type(); for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { - Type::from(ty, types).check(val)?; + Type::from(ty, &instance).check(val)?; } - - let mut cx = LowerContext { - store, - options: &options, - types, - }; if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) { let mut dst = storage[..cnt].iter_mut(); for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { @@ -422,6 +404,8 @@ where flags.set_may_leave(true); + cx.exit_call()?; + return Ok(()); } diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index c5472eb331cc..08104dcb9d18 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -1,9 +1,14 @@ +use crate::component::matching::InstanceType; +use crate::component::ResourceType; use crate::store::{StoreId, StoreOpaque}; use crate::StoreContextMut; use anyhow::{bail, Result}; use std::ptr::NonNull; use std::sync::Arc; -use wasmtime_environ::component::{ComponentTypes, StringEncoding}; +use wasmtime_environ::component::{ComponentTypes, StringEncoding, TypeResourceTableIndex}; +use wasmtime_runtime::component::{ + CallContexts, ComponentInstance, InstanceFlags, ResourceTable, ResourceTables, +}; use wasmtime_runtime::{VMFuncRef, VMMemoryDefinition}; /// Runtime representation of canonical ABI options in the component model. @@ -149,6 +154,11 @@ impl Options { pub fn string_encoding(&self) -> StringEncoding { self.string_encoding } + + /// Returns the id of the store that this `Options` is connected to. + pub fn store_id(&self) -> StoreId { + self.store_id + } } /// A helper structure which is a "package" of the context used during lowering @@ -177,10 +187,37 @@ pub struct LowerContext<'a, T> { /// used for type lookups and general type queries during the /// lifting/lowering process. pub types: &'a ComponentTypes, + + /// A raw unsafe pointer to the component instance that's being lowered + /// into. + /// + /// This pointer is required to be owned by the `store` provided. + instance: *mut ComponentInstance, } #[doc(hidden)] impl<'a, T> LowerContext<'a, T> { + /// Creates a new lowering context from the specified parameters. + /// + /// # Unsafety + /// + /// This function is unsafe as it needs to be guaranteed by the caller that + /// the `instance` here is is valid within `store` and is a valid component + /// instance. + pub unsafe fn new( + store: StoreContextMut<'a, T>, + options: &'a Options, + types: &'a ComponentTypes, + instance: *mut ComponentInstance, + ) -> LowerContext<'a, T> { + LowerContext { + store, + options, + types, + instance, + } + } + /// Returns a view into memory as a mutable slice of bytes. /// /// # Panics @@ -235,6 +272,91 @@ impl<'a, T> LowerContext<'a, T> { .try_into() .unwrap() } + + /// Lowers an `own` resource into the guest, converting the `rep` specified + /// into a guest-local index. + /// + /// The `ty` provided is which table to put this into. + pub fn guest_resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + self.resource_tables().resource_lower_own(Some(ty), rep) + } + + /// Lowers a `borrow` resource into the guest, converting the `rep` to a + /// guest-local index in the `ty` table specified. + pub fn guest_resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + // Implement `lower_borrow`'s special case here where if a borrow is + // inserted into a table owned by the instance which implemented the + // original resource then no borrow tracking is employed and instead the + // `rep` is returned "raw". + // + // This check is performed by comparing the owning instance of `ty` + // against the owning instance of the resource that `ty` is working + // with. + // + // Note that the unsafety here should be valid given the contract of + // `LowerContext::new`. + if unsafe { (*self.instance).resource_owned_by_own_instance(ty) } { + return rep; + } + self.resource_tables().resource_lower_borrow(Some(ty), rep) + } + + /// Lifts a host-owned `own` resource at the `idx` specified into the + /// representation of that resource. + pub fn host_resource_lift_own(&mut self, idx: u32) -> Result { + self.resource_tables().resource_lift_own(None, idx) + } + + /// Lifts a host-owned `borrow` resource at the `idx` specified into the + /// representation of that resource. + pub fn host_resource_lift_borrow(&mut self, idx: u32) -> Result { + self.resource_tables().resource_lift_borrow(None, idx) + } + + /// Lowers a resource into the host-owned table, returning the index it was + /// inserted at. + /// + /// Note that this is a special case for `Resource`. Most of the time a + /// host value shouldn't be lowered with a lowering context. + pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 { + self.resource_tables().resource_lower_own(None, rep) + } + + /// Returns the underlying resource type for the `ty` table specified. + pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { + self.instance_type().resource_type(ty) + } + + /// Returns the instance type information corresponding to the instance that + /// this context is lowering into. + pub fn instance_type(&self) -> InstanceType<'_> { + // Note that the unsafety here should be valid given the contract of + // `LowerContext::new`. + InstanceType::new(unsafe { &*self.instance }) + } + + fn resource_tables(&mut self) -> ResourceTables<'_> { + let (calls, host_table) = self.store.0.component_calls_and_host_table(); + ResourceTables { + host_table: Some(host_table), + calls, + // Note that the unsafety here should be valid given the contract of + // `LowerContext::new`. + tables: Some(unsafe { (*self.instance).component_resource_tables() }), + } + } + + /// Begins a call into the component instance, starting recording of + /// metadata related to resource borrowing. + pub fn enter_call(&mut self) { + self.resource_tables().enter_call() + } + + /// Completes a call into the component instance, validating that it's ok to + /// complete by ensuring the are no remaining active borrows. + pub fn exit_call(&mut self) -> Result<()> { + self.resource_tables().exit_call() + } } /// Contextual information used when lifting a type from a component into the @@ -244,19 +366,55 @@ impl<'a, T> LowerContext<'a, T> { /// operations (or loading from memory). #[doc(hidden)] pub struct LiftContext<'a> { - /// Unlike `LowerContext` lifting doesn't ever need to execute wasm, so a - /// full store isn't required here and only a shared reference is required. - pub store: &'a StoreOpaque, - /// Like lowering, lifting always has options configured. pub options: &'a Options, /// Instance type information, like with lowering. pub types: &'a Arc, + + memory: Option<&'a [u8]>, + + instance: *mut ComponentInstance, + + host_table: &'a mut ResourceTable, + + calls: &'a mut CallContexts, } #[doc(hidden)] impl<'a> LiftContext<'a> { + /// Creates a new lifting context given the provided context. + /// + /// # Unsafety + /// + /// This is unsafe for the same reasons as `LowerContext::new` where the + /// validity of `instance` is required to be upheld by the caller. + pub unsafe fn new( + store: &'a mut StoreOpaque, + options: &'a Options, + types: &'a Arc, + instance: *mut ComponentInstance, + ) -> LiftContext<'a> { + // From `&mut StoreOpaque` provided the goal here is to project out + // three different disjoint fields owned by the store: memory, + // `CallContexts`, and `ResourceTable`. There's no native API for that + // so it's hacked around a bit. This unsafe pointer cast could be fixed + // with more methods in more places, but it doesn't seem worth doing it + // at this time. + let (calls, host_table) = + (&mut *(store as *mut StoreOpaque)).component_calls_and_host_table(); + let memory = options.memory.map(|_| options.memory(store)); + + LiftContext { + memory, + options, + types, + instance, + calls, + host_table, + } + } + /// Returns the entire contents of linear memory for this set of lifting /// options. /// @@ -265,6 +423,88 @@ impl<'a> LiftContext<'a> { /// This will panic if memory has not been configured for this lifting /// operation. pub fn memory(&self) -> &'a [u8] { - self.options.memory(self.store) + self.memory.unwrap() + } + + /// Returns an identifier for the store from which this `LiftContext` was + /// created. + pub fn store_id(&self) -> StoreId { + self.options.store_id + } + + /// Returns the component instance raw pointer that is being lifted from. + pub fn instance_ptr(&self) -> *mut ComponentInstance { + self.instance + } + + /// Lifts an `own` resource from the guest at the `idx` specified into its + /// representation. + /// + /// Additionally returns a destructor/instance flags to go along with the + /// representation so the host knows how to destroy this resource. + pub fn guest_resource_lift_own( + &mut self, + ty: TypeResourceTableIndex, + idx: u32, + ) -> Result<(u32, Option>, Option)> { + let idx = self.resource_tables().resource_lift_own(Some(ty), idx)?; + // Note that the unsafety here should be valid given the contract of + // `LiftContext::new`. + let (dtor, flags) = unsafe { (*self.instance).dtor_and_flags(ty) }; + Ok((idx, dtor, flags)) + } + + /// Lifts a `borrow` resource from the guest at the `idx` specified. + pub fn guest_resource_lift_borrow( + &mut self, + ty: TypeResourceTableIndex, + idx: u32, + ) -> Result { + self.resource_tables().resource_lift_borrow(Some(ty), idx) + } + + /// Lowers a resource into the host-owned table, returning the index it was + /// inserted at. + pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 { + self.resource_tables().resource_lower_own(None, rep) + } + + /// Lowers a resource into the host-owned table, returning the index it was + /// inserted at. + pub fn host_resource_lower_borrow(&mut self, rep: u32) -> u32 { + self.resource_tables().resource_lower_borrow(None, rep) + } + + /// Returns the underlying type of the resource table specified by `ty`. + pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { + self.instance_type().resource_type(ty) + } + + /// Returns instance type information for the component instance that is + /// being lifted from. + pub fn instance_type(&self) -> InstanceType<'_> { + // Note that the unsafety here should be valid given the contract of + // `LiftContext::new`. + InstanceType::new(unsafe { &*self.instance }) + } + + fn resource_tables(&mut self) -> ResourceTables<'_> { + ResourceTables { + host_table: Some(self.host_table), + calls: self.calls, + // Note that the unsafety here should be valid given the contract of + // `LiftContext::new`. + tables: Some(unsafe { (*self.instance).component_resource_tables() }), + } + } + + /// Same as `LowerContext::enter_call` + pub fn enter_call(&mut self) { + self.resource_tables().enter_call() + } + + /// Same as `LiftContext::enter_call` + pub fn exit_call(&mut self) -> Result<()> { + self.resource_tables().exit_call() } } diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs index cf297f0ccf52..1295994daad7 100644 --- a/crates/wasmtime/src/component/func/typed.rs +++ b/crates/wasmtime/src/component/func/typed.rs @@ -1,18 +1,21 @@ use crate::component::func::{Func, LiftContext, LowerContext, Options}; +use crate::component::matching::InstanceType; use crate::component::storage::{storage_as_slice, storage_as_slice_mut}; -use crate::store::StoreOpaque; -use crate::{AsContext, AsContextMut, StoreContext, ValRaw}; +use crate::{AsContextMut, StoreContext, StoreContextMut, ValRaw}; use anyhow::{anyhow, bail, Context, Result}; use std::borrow::Cow; use std::fmt; use std::marker; use std::mem::{self, MaybeUninit}; +use std::ptr::NonNull; use std::str; use std::sync::Arc; use wasmtime_environ::component::{ CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, VariantInfo, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, }; +use wasmtime_runtime::component::ComponentInstance; +use wasmtime_runtime::SendSyncPtr; /// A statically-typed version of [`Func`] which takes `Params` as input and /// returns `Return`. @@ -294,7 +297,7 @@ where /// This is only used when the result fits in the maximum number of stack /// slots. fn lift_stack_result( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, ty: InterfaceType, dst: &Return::Lower, ) -> Result { @@ -304,7 +307,11 @@ where /// Lift the result of a function where the result is stored indirectly on /// the heap. - fn lift_heap_result(cx: &LiftContext<'_>, ty: InterfaceType, dst: &ValRaw) -> Result { + fn lift_heap_result( + cx: &mut LiftContext<'_>, + ty: InterfaceType, + dst: &ValRaw, + ) -> Result { assert!(Return::flatten_count() > MAX_FLAT_RESULTS); // FIXME: needs to read an i64 for memory64 let ptr = usize::try_from(dst.get_u32())?; @@ -431,7 +438,7 @@ pub unsafe trait ComponentType { /// Performs a type-check to see whether this component value type matches /// the interface type `ty` provided. #[doc(hidden)] - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()>; + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()>; } #[doc(hidden)] @@ -523,7 +530,7 @@ pub unsafe trait Lift: Sized + ComponentType { /// Note that this has a default implementation but if `typecheck` passes /// for `Op::Lift` this needs to be overridden. #[doc(hidden)] - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result; + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result; /// Performs the "load" operation in the canonical ABI. /// @@ -539,7 +546,7 @@ pub unsafe trait Lift: Sized + ComponentType { /// Note that this has a default implementation but if `typecheck` passes /// for `Op::Lift` this needs to be overridden. #[doc(hidden)] - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result; + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result; } // Macro to help generate "forwarding implementations" of `ComponentType` to @@ -554,7 +561,7 @@ macro_rules! forward_type_impls { const ABI: CanonicalAbiInfo = <$b as ComponentType>::ABI; #[inline] - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { <$b as ComponentType>::typecheck(ty, types) } } @@ -606,12 +613,12 @@ forward_lowers! { macro_rules! forward_string_lifts { ($($a:ty,)*) => ($( unsafe impl Lift for $a { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { - Ok(::lift(cx, ty, src)?.to_str_from_store(cx.store)?.into()) + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + Ok(::lift(cx, ty, src)?.to_str_from_memory(cx.memory())?.into()) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { - Ok(::load(cx, ty, bytes)?.to_str_from_store(cx.store)?.into()) + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + Ok(::load(cx, ty, bytes)?.to_str_from_memory(cx.memory())?.into()) } } )*) @@ -627,14 +634,14 @@ forward_string_lifts! { macro_rules! forward_list_lifts { ($($a:ty,)*) => ($( unsafe impl Lift for $a { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { let list = as Lift>::lift(cx, ty, src)?; - (0..list.len).map(|index| list.get_from_store(cx.store, index).unwrap()).collect() + (0..list.len).map(|index| list.get_from_store(cx, index).unwrap()).collect() } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { let list = as Lift>::load(cx, ty, bytes)?; - (0..list.len).map(|index| list.get_from_store(cx.store, index).unwrap()).collect() + (0..list.len).map(|index| list.get_from_store(cx, index).unwrap()).collect() } } )*) @@ -656,7 +663,7 @@ macro_rules! integers { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::$abi; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::$ty => Ok(()), other => bail!("expected `{}` found `{}`", desc(&InterfaceType::$ty), desc(other)) @@ -691,13 +698,13 @@ macro_rules! integers { unsafe impl Lift for $primitive { #[inline] - fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(_cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { debug_assert!(matches!(ty, InterfaceType::$ty)); Ok(src.$get() as $primitive) } #[inline] - fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(_cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!(matches!(ty, InterfaceType::$ty)); debug_assert!((bytes.as_ptr() as usize) % Self::SIZE32 == 0); Ok($primitive::from_le_bytes(bytes.try_into().unwrap())) @@ -737,7 +744,7 @@ macro_rules! floats { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::$abi; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::$ty => Ok(()), other => bail!("expected `{}` found `{}`", desc(&InterfaceType::$ty), desc(other)) @@ -773,13 +780,13 @@ macro_rules! floats { unsafe impl Lift for $float { #[inline] - fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(_cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { debug_assert!(matches!(ty, InterfaceType::$ty)); Ok(canonicalize($float::from_bits(src.$get_float()))) } #[inline] - fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(_cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!(matches!(ty, InterfaceType::$ty)); debug_assert!((bytes.as_ptr() as usize) % Self::SIZE32 == 0); Ok(canonicalize($float::from_le_bytes(bytes.try_into().unwrap()))) @@ -798,7 +805,7 @@ unsafe impl ComponentType for bool { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR1; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::Bool => Ok(()), other => bail!("expected `bool` found `{}`", desc(other)), @@ -833,7 +840,7 @@ unsafe impl Lower for bool { unsafe impl Lift for bool { #[inline] - fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(_cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { debug_assert!(matches!(ty, InterfaceType::Bool)); match src.get_i32() { 0 => Ok(false), @@ -842,7 +849,7 @@ unsafe impl Lift for bool { } #[inline] - fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(_cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!(matches!(ty, InterfaceType::Bool)); match bytes[0] { 0 => Ok(false), @@ -856,7 +863,7 @@ unsafe impl ComponentType for char { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::Char => Ok(()), other => bail!("expected `char` found `{}`", desc(other)), @@ -891,13 +898,13 @@ unsafe impl Lower for char { unsafe impl Lift for char { #[inline] - fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(_cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { debug_assert!(matches!(ty, InterfaceType::Char)); Ok(char::try_from(src.get_u32())?) } #[inline] - fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(_cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!(matches!(ty, InterfaceType::Char)); debug_assert!((bytes.as_ptr() as usize) % Self::SIZE32 == 0); let bits = u32::from_le_bytes(bytes.try_into().unwrap()); @@ -916,7 +923,7 @@ unsafe impl ComponentType for str { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::String => Ok(()), other => bail!("expected `string` found `{}`", desc(other)), @@ -1091,7 +1098,7 @@ pub struct WasmStr { } impl WasmStr { - fn new(ptr: usize, len: usize, cx: &LiftContext<'_>) -> Result { + fn new(ptr: usize, len: usize, cx: &mut LiftContext<'_>) -> Result { let byte_len = match cx.options.string_encoding() { StringEncoding::Utf8 => Some(len), StringEncoding::Utf16 => len.checked_mul(2), @@ -1136,33 +1143,33 @@ impl WasmStr { // method that returns `[u16]` after validating to avoid the utf16-to-utf8 // transcode. pub fn to_str<'a, T: 'a>(&self, store: impl Into>) -> Result> { - self.to_str_from_store(store.into().0) + let store = store.into().0; + let memory = self.options.memory(store); + self.to_str_from_memory(memory) } - fn to_str_from_store<'a>(&self, store: &'a StoreOpaque) -> Result> { + fn to_str_from_memory<'a>(&self, memory: &'a [u8]) -> Result> { match self.options.string_encoding() { - StringEncoding::Utf8 => self.decode_utf8(store), - StringEncoding::Utf16 => self.decode_utf16(store, self.len), + StringEncoding::Utf8 => self.decode_utf8(memory), + StringEncoding::Utf16 => self.decode_utf16(memory, self.len), StringEncoding::CompactUtf16 => { if self.len & UTF16_TAG == 0 { - self.decode_latin1(store) + self.decode_latin1(memory) } else { - self.decode_utf16(store, self.len ^ UTF16_TAG) + self.decode_utf16(memory, self.len ^ UTF16_TAG) } } } } - fn decode_utf8<'a>(&self, store: &'a StoreOpaque) -> Result> { - let memory = self.options.memory(store); + fn decode_utf8<'a>(&self, memory: &'a [u8]) -> Result> { // Note that bounds-checking already happen in construction of `WasmStr` // so this is never expected to panic. This could theoretically be // unchecked indexing if we're feeling wild enough. Ok(str::from_utf8(&memory[self.ptr..][..self.len])?.into()) } - fn decode_utf16<'a>(&self, store: &'a StoreOpaque, len: usize) -> Result> { - let memory = self.options.memory(store); + fn decode_utf16<'a>(&self, memory: &'a [u8], len: usize) -> Result> { // See notes in `decode_utf8` for why this is panicking indexing. let memory = &memory[self.ptr..][..len * 2]; Ok(std::char::decode_utf16( @@ -1174,9 +1181,8 @@ impl WasmStr { .into()) } - fn decode_latin1<'a>(&self, store: &'a StoreOpaque) -> Result> { + fn decode_latin1<'a>(&self, memory: &'a [u8]) -> Result> { // See notes in `decode_utf8` for why this is panicking indexing. - let memory = self.options.memory(store); Ok(encoding_rs::mem::decode_latin1( &memory[self.ptr..][..self.len], )) @@ -1190,7 +1196,7 @@ unsafe impl ComponentType for WasmStr { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::String => Ok(()), other => bail!("expected `string` found `{}`", desc(other)), @@ -1199,7 +1205,7 @@ unsafe impl ComponentType for WasmStr { } unsafe impl Lift for WasmStr { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { debug_assert!(matches!(ty, InterfaceType::String)); // FIXME: needs memory64 treatment let ptr = src[0].get_u32(); @@ -1208,7 +1214,7 @@ unsafe impl Lift for WasmStr { WasmStr::new(ptr, len, cx) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!(matches!(ty, InterfaceType::String)); debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0); // FIXME: needs memory64 treatment @@ -1227,9 +1233,9 @@ where const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR; - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { match ty { - InterfaceType::List(t) => T::typecheck(&types[*t].element, types), + InterfaceType::List(t) => T::typecheck(&types.types[*t].element, types), other => bail!("expected `list` found `{}`", desc(other)), } } @@ -1330,6 +1336,7 @@ pub struct WasmList { // reference to something inside a `StoreOpaque`, but that's not easily // available at this time, so it's left as a future exercise. types: Arc, + instance: SendSyncPtr, _marker: marker::PhantomData, } @@ -1337,7 +1344,7 @@ impl WasmList { fn new( ptr: usize, len: usize, - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, elem: InterfaceType, ) -> Result> { match len @@ -1356,6 +1363,7 @@ impl WasmList { options: *cx.options, elem, types: cx.types.clone(), + instance: SendSyncPtr::new(NonNull::new(cx.instance_ptr()).unwrap()), _marker: marker::PhantomData, }) } @@ -1371,23 +1379,33 @@ impl WasmList { /// Returns `None` if `index` is out of bounds. Returns `Some(Err(..))` if /// the value couldn't be decoded (it was invalid). Returns `Some(Ok(..))` /// if the value is valid. + /// + /// # Panics + /// + /// This function will panic if the string did not originally come from the + /// `store` specified. // // TODO: given that interface values are intended to be consumed in one go // should we even expose a random access iteration API? In theory all // consumers should be validating through the iterator. - pub fn get(&self, store: impl AsContext, index: usize) -> Option> { - self.get_from_store(store.as_context().0, index) - } - - fn get_from_store(&self, store: &StoreOpaque, index: usize) -> Option> { + pub fn get(&self, mut store: impl AsContextMut, index: usize) -> Option> { + let store = store.as_context_mut().0; + self.options.store_id().assert_belongs_to(store.id()); + // This should be safe because the unsafety lies in the `self.instance` + // pointer passed in has previously been validated by the lifting + // context this was originally created within and with the check above + // this is guaranteed to be the same store. This means that this should + // be carrying over the original assertion from the original creation of + // the lifting context that created this type. + let mut cx = + unsafe { LiftContext::new(store, &self.options, &self.types, self.instance.as_ptr()) }; + self.get_from_store(&mut cx, index) + } + + fn get_from_store(&self, cx: &mut LiftContext<'_>, index: usize) -> Option> { if index >= self.len { return None; } - let cx = LiftContext { - store, - options: &self.options, - types: &self.types, - }; // Note that this is using panicking indexing and this is expected to // never fail. The bounds-checking here happened during the construction // of the `WasmList` itself which means these should always be in-bounds @@ -1395,7 +1413,7 @@ impl WasmList { // unchecked indexing if we're confident enough and it's actually a perf // issue one day. let bytes = &cx.memory()[self.ptr + index * T::SIZE32..][..T::SIZE32]; - Some(T::load(&cx, self.elem, bytes)) + Some(T::load(cx, self.elem, bytes)) } /// Returns an iterator over the elements of this list. @@ -1404,10 +1422,14 @@ impl WasmList { /// `Result` value of the iterator. pub fn iter<'a, U: 'a>( &'a self, - store: impl Into>, + store: impl Into>, ) -> impl ExactSizeIterator> + 'a { let store = store.into().0; - (0..self.len).map(move |i| self.get_from_store(store, i).unwrap()) + self.options.store_id().assert_belongs_to(store.id()); + // See comments about unsafety in the `get` method. + let mut cx = + unsafe { LiftContext::new(store, &self.options, &self.types, self.instance.as_ptr()) }; + (0..self.len).map(move |i| self.get_from_store(&mut cx, i).unwrap()) } } @@ -1469,13 +1491,13 @@ unsafe impl ComponentType for WasmList { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR; - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { <[T] as ComponentType>::typecheck(ty, types) } } unsafe impl Lift for WasmList { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { let elem = match ty { InterfaceType::List(i) => cx.types[i].element, _ => bad_type_info(), @@ -1487,7 +1509,7 @@ unsafe impl Lift for WasmList { WasmList::new(ptr, len, cx, elem) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { let elem = match ty { InterfaceType::List(i) => cx.types[i].element, _ => bad_type_info(), @@ -1504,12 +1526,12 @@ unsafe impl Lift for WasmList { /// Verify that the given wasm type is a tuple with the expected fields in the right order. fn typecheck_tuple( ty: &InterfaceType, - types: &ComponentTypes, - expected: &[fn(&InterfaceType, &ComponentTypes) -> Result<()>], + types: &InstanceType<'_>, + expected: &[fn(&InterfaceType, &InstanceType<'_>) -> Result<()>], ) -> Result<()> { match ty { InterfaceType::Tuple(t) => { - let tuple = &types[*t]; + let tuple = &types.types[*t]; if tuple.types.len() != expected.len() { bail!( "expected {}-tuple, found {}-tuple", @@ -1530,12 +1552,12 @@ fn typecheck_tuple( /// names. pub fn typecheck_record( ty: &InterfaceType, - types: &ComponentTypes, - expected: &[(&str, fn(&InterfaceType, &ComponentTypes) -> Result<()>)], + types: &InstanceType<'_>, + expected: &[(&str, fn(&InterfaceType, &InstanceType<'_>) -> Result<()>)], ) -> Result<()> { match ty { InterfaceType::Record(index) => { - let fields = &types[*index].fields; + let fields = &types.types[*index].fields; if fields.len() != expected.len() { bail!( @@ -1564,15 +1586,15 @@ pub fn typecheck_record( /// names. pub fn typecheck_variant( ty: &InterfaceType, - types: &ComponentTypes, + types: &InstanceType<'_>, expected: &[( &str, - Option Result<()>>, + Option) -> Result<()>>, )], ) -> Result<()> { match ty { InterfaceType::Variant(index) => { - let cases = &types[*index].cases; + let cases = &types.types[*index].cases; if cases.len() != expected.len() { bail!( @@ -1608,10 +1630,14 @@ pub fn typecheck_variant( /// Verify that the given wasm type is a enum with the expected cases in the right order and with the right /// names. -pub fn typecheck_enum(ty: &InterfaceType, types: &ComponentTypes, expected: &[&str]) -> Result<()> { +pub fn typecheck_enum( + ty: &InterfaceType, + types: &InstanceType<'_>, + expected: &[&str], +) -> Result<()> { match ty { InterfaceType::Enum(index) => { - let names = &types[*index].names; + let names = &types.types[*index].names; if names.len() != expected.len() { bail!( @@ -1636,12 +1662,12 @@ pub fn typecheck_enum(ty: &InterfaceType, types: &ComponentTypes, expected: &[&s /// Verify that the given wasm type is a union with the expected cases in the right order. pub fn typecheck_union( ty: &InterfaceType, - types: &ComponentTypes, - expected: &[fn(&InterfaceType, &ComponentTypes) -> Result<()>], + types: &InstanceType<'_>, + expected: &[fn(&InterfaceType, &InstanceType<'_>) -> Result<()>], ) -> Result<()> { match ty { InterfaceType::Union(index) => { - let union_types = &types[*index].types; + let union_types = &types.types[*index].types; if union_types.len() != expected.len() { bail!( @@ -1665,12 +1691,12 @@ pub fn typecheck_union( /// names. pub fn typecheck_flags( ty: &InterfaceType, - types: &ComponentTypes, + types: &InstanceType<'_>, expected: &[&str], ) -> Result<()> { match ty { InterfaceType::Flags(index) => { - let names = &types[*index].names; + let names = &types.types[*index].names; if names.len() != expected.len() { bail!( @@ -1718,9 +1744,9 @@ where const ABI: CanonicalAbiInfo = CanonicalAbiInfo::variant_static(&[None, Some(T::ABI)]); - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { match ty { - InterfaceType::Option(t) => T::typecheck(&types[*t].ty, types), + InterfaceType::Option(t) => T::typecheck(&types.types[*t].ty, types), other => bail!("expected `option` found `{}`", desc(other)), } } @@ -1796,7 +1822,7 @@ unsafe impl Lift for Option where T: Lift, { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { let payload = match ty { InterfaceType::Option(ty) => cx.types[ty].ty, _ => bad_type_info(), @@ -1808,7 +1834,7 @@ where }) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0); let payload_ty = match ty { InterfaceType::Option(ty) => cx.types[ty].ty, @@ -1847,10 +1873,10 @@ where const ABI: CanonicalAbiInfo = CanonicalAbiInfo::variant_static(&[Some(T::ABI), Some(E::ABI)]); - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::Result(r) => { - let result = &types[*r]; + let result = &types.types[*r]; match &result.ok { Some(ty) => T::typecheck(ty, types)?, None if T::IS_RUST_UNIT_TYPE => {} @@ -2052,7 +2078,7 @@ where T: Lift, E: Lift, { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { let (ok, err) = match ty { InterfaceType::Result(ty) => { let ty = &cx.types[ty]; @@ -2086,7 +2112,7 @@ where }) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0); let discrim = bytes[0]; let payload = &bytes[Self::INFO.payload_offset32 as usize..]; @@ -2105,7 +2131,7 @@ where } } -fn lift_option(cx: &LiftContext<'_>, ty: Option, src: &T::Lower) -> Result +fn lift_option(cx: &mut LiftContext<'_>, ty: Option, src: &T::Lower) -> Result where T: Lift, { @@ -2115,7 +2141,7 @@ where } } -fn load_option(cx: &LiftContext<'_>, ty: Option, bytes: &[u8]) -> Result +fn load_option(cx: &mut LiftContext<'_>, ty: Option, bytes: &[u8]) -> Result where T: Lift, { @@ -2166,7 +2192,7 @@ macro_rules! impl_component_ty_for_tuples { fn typecheck( ty: &InterfaceType, - types: &ComponentTypes, + types: &InstanceType<'_>, ) -> Result<()> { typecheck_tuple(ty, types, &[$($t::typecheck),*]) } @@ -2220,7 +2246,7 @@ macro_rules! impl_component_ty_for_tuples { unsafe impl<$($t,)*> Lift for ($($t,)*) where $($t: Lift),* { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, _src: &Self::Lower) -> Result { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, _src: &Self::Lower) -> Result { let types = match ty { InterfaceType::Tuple(t) => &cx.types[t].types, _ => bad_type_info(), @@ -2235,7 +2261,7 @@ macro_rules! impl_component_ty_for_tuples { )*)) } - fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0); let types = match ty { InterfaceType::Tuple(t) => &cx.types[t].types, @@ -2260,7 +2286,7 @@ macro_rules! impl_component_ty_for_tuples { for_each_function_signature!(impl_component_ty_for_tuples); -fn desc(ty: &InterfaceType) -> &'static str { +pub fn desc(ty: &InterfaceType) -> &'static str { match ty { InterfaceType::U8 => "u8", InterfaceType::S8 => "s8", @@ -2285,6 +2311,8 @@ fn desc(ty: &InterfaceType) -> &'static str { InterfaceType::Flags(_) => "flags", InterfaceType::Enum(_) => "enum", InterfaceType::Union(_) => "union", + InterfaceType::Own(_) => "owned resource", + InterfaceType::Borrow(_) => "borrowed resource", } } diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 2eeacd451596..eaf6065cc41d 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -1,5 +1,7 @@ +use super::component::AllCallFuncPointers; use crate::component::func::HostFunc; -use crate::component::{Component, ComponentNamedList, Func, Lift, Lower, TypedFunc}; +use crate::component::matching::InstanceType; +use crate::component::{Component, ComponentNamedList, Func, Lift, Lower, ResourceType, TypedFunc}; use crate::instance::OwnedImports; use crate::linker::DefinitionType; use crate::store::{StoreOpaque, Stored}; @@ -7,16 +9,15 @@ use crate::{AsContextMut, Module, StoreContextMut}; use anyhow::{anyhow, Context, Result}; use indexmap::IndexMap; use std::marker; +use std::ptr::NonNull; use std::sync::Arc; -use wasmtime_environ::component::{ - AlwaysTrap, ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory, - ExtractPostReturn, ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport, - RuntimeImportIndex, RuntimeInstanceIndex, Transcoder, -}; -use wasmtime_environ::{EntityIndex, EntityType, Global, PrimaryMap, WasmType}; +use wasmtime_environ::component::*; +use wasmtime_environ::{EntityIndex, EntityType, Global, PrimaryMap, SignatureIndex, WasmType}; use wasmtime_runtime::component::{ComponentInstance, OwnedComponentInstance}; - -use super::component::AllCallFuncPointers; +use wasmtime_runtime::{ + VMArrayCallFunction, VMFuncRef, VMNativeCallFunction, VMSharedSignatureIndex, + VMWasmCallFunction, +}; /// An instantiated component. /// @@ -121,7 +122,7 @@ impl Instance { /// Looks up a module by name within this [`Instance`]. /// /// The `store` specified must be the store that this instance lives within - /// and `name` is the name of the function to lookup. If the function is + /// and `name` is the name of the function to lookup. If the module is /// found `Some` is returned otherwise `None` is returned. /// /// # Panics @@ -133,6 +134,19 @@ impl Instance { .module(name) .cloned() } + + /// Looks up an exported resource type by name within this [`Instance`]. + /// + /// The `store` specified must be the store that this instance lives within + /// and `name` is the name of the function to lookup. If the resource type + /// is found `Some` is returned otherwise `None` is returned. + /// + /// # Panics + /// + /// Panics if `store` does not own this instance. + pub fn get_resource(&self, mut store: impl AsContextMut, name: &str) -> Option { + self.exports(store.as_context_mut()).root().resource(name) + } } impl InstanceData { @@ -163,6 +177,21 @@ impl InstanceData { func_ref: self.state.transcoder_func_ref(*idx), }) } + CoreDef::ResourceNew(idx) => { + wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction { + func_ref: self.state.resource_new_func_ref(*idx), + }) + } + CoreDef::ResourceRep(idx) => { + wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction { + func_ref: self.state.resource_rep_func_ref(*idx), + }) + } + CoreDef::ResourceDrop(idx) => { + wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction { + func_ref: self.state.resource_drop_func_ref(*idx), + }) + } } } @@ -198,9 +227,29 @@ impl InstanceData { &self.state } + pub fn instance_ptr(&self) -> *mut ComponentInstance { + self.state.instance_ptr() + } + pub fn component_types(&self) -> &Arc { self.component.types() } + + pub fn ty(&self) -> InstanceType<'_> { + InstanceType::new(self.instance()) + } + + // NB: This method is only intended to be called during the instantiation + // process because the `Arc::get_mut` here is fallible and won't generally + // succeed once the instance has been handed to the embedder. Before that + // though it should be guaranteed that the single owning reference currently + // lives within the `ComponentInstance` that's being built. + fn resource_types_mut(&mut self) -> &mut ImportedResources { + Arc::get_mut(self.state.resource_types_mut()) + .unwrap() + .downcast_mut() + .unwrap() + } } struct Instantiator<'a> { @@ -210,12 +259,32 @@ struct Instantiator<'a> { imports: &'a PrimaryMap, } -#[derive(Clone)] -pub enum RuntimeImport { +pub(crate) enum RuntimeImport { Func(Arc), Module(Module), + Resource { + ty: ResourceType, + + // A strong reference to the host function that represents the + // destructor for this resource. At this time all resources here are + // host-defined resources. Note that this is itself never read because + // the funcref below points to it. + // + // Also note that the `Arc` here is used to support the same host + // function being used across multiple instances simultaneously. Or + // otherwise this makes `InstancePre::instantiate` possible to create + // separate instances all sharing the same host function. + _dtor: Arc, + + // A raw function which is filled out (including `wasm_call`) which + // points to the internals of the `_dtor` field. This is read and + // possibly executed by wasm. + dtor_funcref: VMFuncRef, + }, } +pub type ImportedResources = PrimaryMap; + impl<'a> Instantiator<'a> { fn new( component: &'a Component, @@ -224,6 +293,8 @@ impl<'a> Instantiator<'a> { ) -> Instantiator<'a> { let env_component = component.env_component(); store.modules_mut().register_component(component); + let imported_resources: ImportedResources = + PrimaryMap::with_capacity(env_component.imported_resources.len()); Instantiator { component, imports, @@ -231,7 +302,11 @@ impl<'a> Instantiator<'a> { data: InstanceData { instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize), component: component.clone(), - state: OwnedComponentInstance::new(component.runtime_info(), store.traitobj()), + state: OwnedComponentInstance::new( + component.runtime_info(), + Arc::new(imported_resources), + store.traitobj(), + ), imports: imports.clone(), }, } @@ -239,6 +314,22 @@ impl<'a> Instantiator<'a> { fn run(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> { let env_component = self.component.env_component(); + + // Before all initializers are processed configure all destructors for + // host-defined resources. No initializer will correspond to these and + // it's required to happen before they're needed, so execute this first. + for (idx, import) in env_component.imported_resources.iter() { + let (ty, func_ref) = match &self.imports[*import] { + RuntimeImport::Resource { + ty, dtor_funcref, .. + } => (*ty, NonNull::from(dtor_funcref)), + _ => unreachable!(), + }; + let i = self.data.resource_types_mut().push(ty); + assert_eq!(i, idx); + self.data.state.set_resource_destructor(idx, Some(func_ref)); + } + for initializer in env_component.initializers.iter() { match initializer { GlobalInitializer::InstantiateModule(m) => { @@ -301,6 +392,11 @@ impl<'a> Instantiator<'a> { } GlobalInitializer::Transcoder(e) => self.transcoder(e), + + GlobalInitializer::Resource(r) => self.resource(store.0, r), + GlobalInitializer::ResourceNew(r) => self.resource_new(r), + GlobalInitializer::ResourceRep(r) => self.resource_rep(r), + GlobalInitializer::ResourceDrop(r) => self.resource_drop(r), } } Ok(()) @@ -331,40 +427,102 @@ impl<'a> Instantiator<'a> { ); } - fn always_trap(&mut self, trap: &AlwaysTrap) { - let AllCallFuncPointers { - wasm_call, - array_call, - native_call, - } = self.component.always_trap_ptrs(trap.index); - let signature = self - .component - .signatures() - .shared_signature(trap.canonical_abi) - .expect("found unregistered signature"); - self.data - .state - .set_always_trap(trap.index, wasm_call, native_call, array_call, signature); - } - - fn transcoder(&mut self, transcoder: &Transcoder) { + fn set_funcref( + &mut self, + index: T, + signature: SignatureIndex, + func_ptrs: impl FnOnce(&Component, T) -> AllCallFuncPointers, + set_funcref: impl FnOnce( + &mut OwnedComponentInstance, + T, + NonNull, + NonNull, + VMArrayCallFunction, + VMSharedSignatureIndex, + ), + ) { let AllCallFuncPointers { wasm_call, array_call, native_call, - } = self.component.transcoder_ptrs(transcoder.index); + } = func_ptrs(&self.component, index); let signature = self .component .signatures() - .shared_signature(transcoder.signature) + .shared_signature(signature) .expect("found unregistered signature"); - self.data.state.set_transcoder( - transcoder.index, + set_funcref( + &mut self.data.state, + index, wasm_call, native_call, array_call, signature, - ); + ) + } + + fn always_trap(&mut self, trap: &AlwaysTrap) { + self.set_funcref( + trap.index, + trap.canonical_abi, + Component::always_trap_ptrs, + OwnedComponentInstance::set_always_trap, + ) + } + + fn transcoder(&mut self, transcoder: &Transcoder) { + self.set_funcref( + transcoder.index, + transcoder.signature, + Component::transcoder_ptrs, + OwnedComponentInstance::set_transcoder, + ) + } + + fn resource(&mut self, store: &mut StoreOpaque, resource: &Resource) { + let dtor = resource + .dtor + .as_ref() + .map(|dtor| self.data.lookup_def(store, dtor)); + let dtor = dtor.map(|export| match export { + wasmtime_runtime::Export::Function(f) => f.func_ref, + _ => unreachable!(), + }); + let index = self + .component + .env_component() + .resource_index(resource.index); + self.data.state.set_resource_destructor(index, dtor); + let ty = ResourceType::guest(store.id(), &self.data.state, resource.index); + let i = self.data.resource_types_mut().push(ty); + debug_assert_eq!(i, index); + } + + fn resource_new(&mut self, resource: &ResourceNew) { + self.set_funcref( + resource.index, + resource.signature, + Component::resource_new_ptrs, + OwnedComponentInstance::set_resource_new, + ) + } + + fn resource_rep(&mut self, resource: &ResourceRep) { + self.set_funcref( + resource.index, + resource.signature, + Component::resource_rep_ptrs, + OwnedComponentInstance::set_resource_rep, + ) + } + + fn resource_drop(&mut self, resource: &ResourceDrop) { + self.set_funcref( + resource.index, + resource.signature, + Component::resource_drop_ptrs, + OwnedComponentInstance::set_resource_drop, + ) } fn extract_memory(&mut self, store: &mut StoreOpaque, memory: &ExtractMemory) { @@ -656,7 +814,7 @@ impl<'a, 'store> ExportInstance<'a, 'store> { .func(name) .ok_or_else(|| anyhow!("failed to find function export `{}`", name))?; Ok(func - ._typed::(self.store) + ._typed::(self.store, Some(self.data)) .with_context(|| format!("failed to convert function `{}` to given type", name))?) } @@ -672,6 +830,18 @@ impl<'a, 'store> ExportInstance<'a, 'store> { } } + /// Same as [`Instance::get_resource`] + pub fn resource(&mut self, name: &str) -> Option { + match self.exports.get(name)? { + Export::Type(TypeDef::Resource(id)) => Some(self.data.ty().resource_type(*id)), + Export::Type(_) + | Export::LiftedFunction { .. } + | Export::ModuleStatic(_) + | Export::ModuleImport(_) + | Export::Instance(_) => None, + } + } + /// Returns an iterator of all of the exported modules that this instance /// contains. // diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs index c97308fa0685..15469923fe8a 100644 --- a/crates/wasmtime/src/component/linker.rs +++ b/crates/wasmtime/src/component/linker.rs @@ -1,7 +1,9 @@ use crate::component::func::HostFunc; use crate::component::instance::RuntimeImport; use crate::component::matching::TypeChecker; -use crate::component::{Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, Val}; +use crate::component::{ + Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, ResourceType, Val, +}; use crate::{AsContextMut, Engine, Module, StoreContextMut}; use anyhow::{anyhow, bail, Context, Result}; use indexmap::IndexMap; @@ -50,13 +52,14 @@ pub struct LinkerInstance<'a, T> { _marker: marker::PhantomData T>, } -pub type NameMap = HashMap; +pub(crate) type NameMap = HashMap; #[derive(Clone)] -pub enum Definition { +pub(crate) enum Definition { Instance(NameMap), Func(Arc), Module(Module), + Resource(ResourceType, Arc), } impl Linker { @@ -129,9 +132,11 @@ impl Linker { /// `component` imports or if a name defined doesn't match the type of the /// item imported by the `component` provided. pub fn instantiate_pre(&self, component: &Component) -> Result> { - let cx = TypeChecker { + let mut cx = TypeChecker { + component: component.env_component(), types: component.types(), strings: &self.strings, + imported_resources: Default::default(), }; // Walk over the component's list of import names and use that to lookup @@ -170,6 +175,11 @@ impl Linker { let import = match cur { Definition::Module(m) => RuntimeImport::Module(m.clone()), Definition::Func(f) => RuntimeImport::Func(f.clone()), + Definition::Resource(t, dtor) => RuntimeImport::Resource { + ty: t.clone(), + _dtor: dtor.clone(), + dtor_funcref: component.resource_drop_func_ref(dtor), + }, // This is guaranteed by the compilation process that "leaf" // runtime imports are never instances. @@ -367,6 +377,40 @@ impl LinkerInstance<'_, T> { self.insert(name, Definition::Module(module.clone())) } + /// Defines a new host [`ResourceType`] in this linker. + /// + /// This function is used to specify resources defined in the host. The `U` + /// type parameter here is the type parameter to [`Resource`]. This means + /// that component types using this resource type will be visible in + /// Wasmtime as [`Resource`]. + /// + /// The `name` argument is the name to define the resource within this + /// linker. + /// + /// The `dtor` provided is a destructor that will get invoked when an owned + /// version of this resource is destroyed from the guest. Note that this + /// destructor is not called when a host-owned resource is destroyed as it's + /// assumed the host knows how to handle destroying its own resources. + /// + /// The `dtor` closure is provided the store state as the first argument + /// along with the representation of the resource that was just destroyed. + /// + /// [`Resource`]: crate::component::Resource + pub fn resource( + &mut self, + name: &str, + dtor: impl Fn(StoreContextMut<'_, T>, u32) + Send + Sync + 'static, + ) -> Result<()> { + let name = self.strings.intern(name); + let dtor = Arc::new(crate::func::HostFunc::wrap( + &self.engine, + move |mut cx: crate::Caller<'_, T>, param: u32| { + dtor(cx.as_context_mut(), param); + }, + )); + self.insert(name, Definition::Resource(ResourceType::host::(), dtor)) + } + /// Defines a nested instance within this instance. /// /// This can be used to describe arbitrarily nested levels of instances diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs index 0f843032b97c..c68a4995d4b4 100644 --- a/crates/wasmtime/src/component/matching.rs +++ b/crates/wasmtime/src/component/matching.rs @@ -1,20 +1,38 @@ use crate::component::func::HostFunc; use crate::component::linker::{Definition, NameMap, Strings}; +use crate::component::ResourceType; use crate::types::matching; use crate::Module; use anyhow::{anyhow, bail, Context, Result}; +use std::any::Any; use std::sync::Arc; use wasmtime_environ::component::{ - ComponentTypes, TypeComponentInstance, TypeDef, TypeFuncIndex, TypeModule, + ComponentTypes, ResourceIndex, TypeComponentInstance, TypeDef, TypeFuncIndex, TypeModule, + TypeResourceTableIndex, }; +use wasmtime_environ::PrimaryMap; +use wasmtime_runtime::component::ComponentInstance; pub struct TypeChecker<'a> { pub types: &'a Arc, + pub component: &'a wasmtime_environ::component::Component, pub strings: &'a Strings, + pub imported_resources: Arc>, +} + +#[derive(Copy, Clone)] +#[doc(hidden)] +pub struct InstanceType<'a> { + pub types: &'a Arc, + pub resources: &'a Arc>, } impl TypeChecker<'_> { - pub fn definition(&self, expected: &TypeDef, actual: Option<&Definition>) -> Result<()> { + pub(crate) fn definition( + &mut self, + expected: &TypeDef, + actual: Option<&Definition>, + ) -> Result<()> { match *expected { TypeDef::Module(t) => match actual { Some(Definition::Module(actual)) => self.module(&self.types[t], actual), @@ -32,6 +50,54 @@ impl TypeChecker<'_> { TypeDef::Component(_) => bail!("expected component found {}", desc(actual)), TypeDef::Interface(_) => bail!("expected type found {}", desc(actual)), + TypeDef::Resource(i) => { + let i = self.types[i].ty; + let actual = match actual { + Some(Definition::Resource(actual, _dtor)) => actual, + + // If a resource is imported yet nothing was supplied then + // that's only successful if the resource has itself + // already been defined. If it's already defined then that + // means that this is an `(eq ...)` import which is not + // required to be satisfied via `Linker` definitions in the + // Wasmtime API. + None if self.imported_resources.get(i).is_some() => return Ok(()), + + _ => bail!("expected resource found {}", desc(actual)), + }; + + match self.imported_resources.get(i) { + // If `i` hasn't been pushed onto `imported_resources` yet + // then that means that it's the first time a new resource + // was introduced, so record the type of this resource. It + // should always be the case that the next index assigned + // is equal to `i` since types should be checked in the + // same order they were assigned into the `Component` type. + // + // Note the `get_mut` here which is expected to always + // succeed since `imported_resources` has not yet been + // cloned. + None => { + let resources = Arc::get_mut(&mut self.imported_resources).unwrap(); + let id = resources.push(*actual); + assert_eq!(id, i); + } + + // If `i` has been defined, however, then that means that + // this is an `(eq ..)` bounded type imported because it's + // referring to a previously defined type. In this + // situation it's not required to provide a type import but + // if it's supplied then it must be equal. In this situation + // it's supplied, so test for equality. + Some(expected) => { + if expected != actual { + bail!("mismatched resource types"); + } + } + } + Ok(()) + } + // not possible for valid components to import TypeDef::CoreFunc(_) => unreachable!(), } @@ -68,7 +134,11 @@ impl TypeChecker<'_> { Ok(()) } - fn instance(&self, expected: &TypeComponentInstance, actual: Option<&NameMap>) -> Result<()> { + fn instance( + &mut self, + expected: &TypeComponentInstance, + actual: Option<&NameMap>, + ) -> Result<()> { // Like modules, every export in the expected type must be present in // the actual type. It's ok, though, to have extra exports in the actual // type. @@ -90,7 +160,11 @@ impl TypeChecker<'_> { } fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> { - actual.typecheck(expected, self.types) + let instance_type = InstanceType { + types: self.types, + resources: &self.imported_resources, + }; + actual.typecheck(expected, &instance_type) } } @@ -107,6 +181,35 @@ impl Definition { Definition::Module(_) => "module", Definition::Func(_) => "func", Definition::Instance(_) => "instance", + Definition::Resource(..) => "resource", } } } + +impl<'a> InstanceType<'a> { + pub fn new(instance: &'a ComponentInstance) -> InstanceType<'a> { + InstanceType { + types: instance.component_types(), + resources: downcast_arc_ref(instance.resource_types()), + } + } + + pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType { + let index = self.types[index].ty; + self.resources[index] + } +} + +/// Small helper method to downcast an `Arc` borrow into a borrow of a concrete +/// type within the `Arc`. +/// +/// Note that this is differnet than `downcast_ref` which projects out `&T` +/// where here we want `&Arc`. +fn downcast_arc_ref(arc: &Arc) -> &Arc { + // First assert that the payload of the `Any` is indeed a `T` + let _ = arc.downcast_ref::(); + + // Next do an unsafe pointer cast to convert the `Any` into `T` which should + // be safe given the above check. + unsafe { &*(arc as *const Arc as *const Arc) } +} diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs index 229b90845766..a486ebe68850 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -10,6 +10,7 @@ mod func; mod instance; mod linker; mod matching; +mod resources; mod storage; mod store; pub mod types; @@ -20,7 +21,8 @@ pub use self::func::{ }; pub use self::instance::{ExportInstance, Exports, Instance, InstancePre}; pub use self::linker::{Linker, LinkerInstance}; -pub use self::types::Type; +pub use self::resources::{Resource, ResourceAny}; +pub use self::types::{ResourceType, Type}; pub use self::values::{ Enum, Flags, List, OptionVal, Record, ResultVal, Tuple, Union, Val, Variant, }; @@ -36,6 +38,7 @@ pub mod __internal { typecheck_record, typecheck_union, typecheck_variant, ComponentVariant, LiftContext, LowerContext, MaybeUninitExt, Options, }; + pub use super::matching::InstanceType; pub use crate::map_maybe_uninit; pub use crate::store::StoreOpaque; pub use anyhow; diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs new file mode 100644 index 000000000000..ded0665a66a1 --- /dev/null +++ b/crates/wasmtime/src/component/resources.rs @@ -0,0 +1,716 @@ +use crate::component::func::{bad_type_info, desc, LiftContext, LowerContext}; +use crate::component::matching::InstanceType; +use crate::component::{ComponentType, Lift, Lower}; +use crate::store::{StoreId, StoreOpaque}; +use crate::{AsContextMut, StoreContextMut, Trap}; +use anyhow::{bail, Result}; +use std::any::TypeId; +use std::fmt; +use std::marker; +use std::mem::MaybeUninit; +use std::sync::atomic::{AtomicU32, Ordering::Relaxed}; +use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType}; +use wasmtime_runtime::component::{ComponentInstance, InstanceFlags, ResourceTables}; +use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw}; + +/// Representation of a resource type in the component model. +/// +/// Resources are currently always represented as 32-bit integers but they have +/// unique types across instantiations and the host. For example instantiating +/// the same component twice means that defined resource types in the component +/// will all be different. Values of this type can be compared to see if +/// resources have the same type. +/// +/// Resource types can also be defined on the host in addition to guests. On the +/// host resource types are tied to a `T`, an arbitrary Rust type. Two host +/// resource types are the same if they point to the same `T`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ResourceType { + kind: ResourceTypeKind, +} + +impl ResourceType { + /// Creates a new host resource type corresponding to `T`. + /// + /// Note that `T` is a mostly a phantom type parameter here. It does not + /// need to reflect the actual storage of the resource `T`. For example this + /// is valid: + /// + /// ```rust + /// use wasmtime::component::ResourceType; + /// + /// struct Foo; + /// + /// let ty = ResourceType::host::(); + /// ``` + /// + /// A resource type of type `ResourceType::host::()` will match the type + /// of the value produced by `Resource::::new_{own,borrow}`. + pub fn host() -> ResourceType { + ResourceType { + kind: ResourceTypeKind::Host(TypeId::of::()), + } + } + + pub(crate) fn guest( + store: StoreId, + instance: &ComponentInstance, + id: DefinedResourceIndex, + ) -> ResourceType { + ResourceType { + kind: ResourceTypeKind::Guest { + store, + instance: instance as *const _ as usize, + id, + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum ResourceTypeKind { + Host(TypeId), + Guest { + store: StoreId, + // For now this is the `*mut ComponentInstance` pointer within the store + // that this guest corresponds to. It's used to distinguish different + // instantiations of the same component within the store. + instance: usize, + id: DefinedResourceIndex, + }, +} + +/// A host-defined resource in the component model. +/// +/// This type can be thought of as roughly a newtype wrapper around `u32` for +/// use as a resource with the component model. The main guarantee that the +/// component model provides is that the `u32` is not forgeable by guests and +/// there are guaranteed semantics about when a `u32` may be in use by the guest +/// and when it's guaranteed no longer needed. This means that it is safe for +/// embedders to consider the internal `u32` representation "trusted" and use it +/// for things like table indices with infallible accessors that panic on +/// out-of-bounds. This should only panic for embedder bugs, not because of any +/// possible behavior in the guest. +/// +/// A `Resource` value dynamically represents both an `(own $t)` in the +/// component model as well as a `(borrow $t)`. It can be inspected via +/// [`Resource::owned`] to test whether it is an owned handle. An owned handle +/// which is not actively borrowed can be destroyed at any time as it's +/// guaranteed that the guest does not have access to it. A borrowed handle, on +/// the other hand, may be accessed by the guest so it's not necessarily +/// guaranteed to be able to be destroyed. +/// +/// Note that the "own" and "borrow" here refer to the component model, not +/// Rust. The semantics of Rust ownership and borrowing are slightly different +/// than the component model's (but spiritually the same) in that more dynamic +/// tracking is employed as part of the component model. This means that it's +/// possible to get runtime errors when using a `Resource`. For example it is +/// an error to call [`Resource::new_borrow`] and pass that to a component +/// function expecting `(own $t)` and this is not statically disallowed. +/// +/// The [`Resource`] type implements both the [`Lift`] and [`Lower`] trait to be +/// used with typed functions in the component model or as part of aggregate +/// structures and datatypes. +/// +/// # Destruction of a resource +/// +/// Resources in the component model are optionally defined with a destructor, +/// but this host resource type does not specify a destructor. It is left up to +/// the embedder to be able to determine how best to a destroy a resource when +/// it is owned. +/// +/// Note, though, that while [`Resource`] itself does not specify destructors +/// it's still possible to do so via the [`Linker::resource`] definition. When a +/// resource type is defined for a guest component a destructor can be specified +/// which can be used to hook into resource destruction triggered by the guest. +/// +/// This means that there are two ways that resource destruction is handled: +/// +/// * Host resources destroyed by the guest can hook into the +/// [`Linker::resource`] destructor closure to handle resource destruction. +/// This could, for example, remove table entries. +/// +/// * Host resources owned by the host itself have no automatic means of +/// destruction. The host can make its own determination that its own resource +/// is not lent out to the guest and at that time choose to destroy or +/// deallocate it. +/// +/// # Dynamic behavior of a resource +/// +/// A host-defined [`Resource`] does not necessarily represent a static value. +/// Its internals may change throughout its usage to track the state associated +/// with the resource. The internal 32-bit host-defined representation never +/// changes, however. +/// +/// For example if there's a component model function of the form: +/// +/// ```wasm +/// (func (param "a" (borrow $t)) (param "b" (own $t))) +/// ``` +/// +/// Then that can be extracted in Rust with: +/// +/// ```rust,ignore +/// let func = instance.get_typed_func::<(&Resource, &Resource), ()>(&mut store, "name")?; +/// ``` +/// +/// Here the exact same resource can be provided as both arguments but that is +/// not valid to do so because the same resource cannot be actively borrowed and +/// passed by-value as the second parameter at the same time. The internal state +/// in `Resource` will track this information and provide a dynamic runtime +/// error in this situation. +/// +/// Mostly it's important to be aware that there is dynamic state associated +/// with a [`Resource`] to provide errors in situations that cannot be +/// statically ruled out. +/// +/// # Borrows and host responsibilities +/// +/// Borrows to resources in the component model are guaranteed to be transient +/// such that if a borrow is passed as part of a function call then when the +/// function returns it's guaranteed that the guest no longer has access to the +/// resource. This guarantee, however, must be manually upheld by the host when +/// it receives its own borrow. +/// +/// As mentioned above the [`Resource`] type can represent a borrowed value +/// in addition to an owned value. This means a guest can provide the host with +/// a borrow, such as an argument to an imported function: +/// +/// ```rust,ignore +/// linker.root().func_wrap("name", |_cx, (r,): (Resource,)| { +/// assert!(!r.owned()); +/// // .. here `r` is a borrowed value provided by the guest and the host +/// // shouldn't continue to access it beyond the scope of this call +/// })?; +/// ``` +/// +/// In this situation the host should take care to not attempt to persist the +/// resource beyond the scope of the call. It's the host's resource so it +/// technically can do what it wants with it but nothing is statically +/// preventing `r` to stay pinned to the lifetime of the closure invocation. +/// It's considered a mistake that the host performed if `r` is persisted too +/// long and accessed at the wrong time. +/// +/// [`Linker::resource`]: crate::component::LinkerInstance::resource +pub struct Resource { + /// The host-defined 32-bit representation of this resource. + rep: u32, + + /// Dear rust please consider `T` used even though it's not actually used. + _marker: marker::PhantomData T>, + + /// Internal dynamic state tracking for this resource. This can be one of + /// four different states: + /// + /// * `BORROW` / `u32::MAX` - this indicates that this is a borrowed + /// resource. The `rep` doesn't live in the host table and this `Resource` + /// instance is transiently available. It's the host's responsibility to + /// discard this resource when the borrow duration has finished. + /// + /// * `NOT_IN_TABLE` / `u32::MAX - 1` - this indicates that this is an owned + /// resource not present in any store's stable. This resource is not lent + /// out. It can be passed as an `(own $t)` directly into a guest's table + /// or it can be passed as a borrow to a guest which will insert it into + /// a host store's table for dynamic borrow tracking. + /// + /// * `TAKEN` / `u32::MAX - 2` - while the `rep` is available the resource + /// has been dynamically moved into a guest and cannot be moved in again. + /// This is used for example to prevent the same resource from being + /// passed twice to a guest. + /// + /// * All other values - any other value indicates that the value is an + /// index into a store's table of host resources. It's guaranteed that the + /// table entry represents a host resource and the resource may have + /// borrow tracking associated with it. + /// + /// Note that this is an `AtomicU32` but it's not intended to actually be + /// used in conjunction with threads as generally a `Store` lives on one + /// thread at a time. The `AtomicU32` here is used to ensure that this type + /// is `Send + Sync` when captured as a reference to make async programming + /// more ergonomic. + state: AtomicU32, +} + +// See comments on `state` above for info about these values. +const BORROW: u32 = u32::MAX; +const NOT_IN_TABLE: u32 = u32::MAX - 1; +const TAKEN: u32 = u32::MAX - 2; + +fn host_resource_tables(store: &mut StoreOpaque) -> ResourceTables<'_> { + let (calls, host_table) = store.component_calls_and_host_table(); + ResourceTables { + calls, + host_table: Some(host_table), + tables: None, + } +} + +impl Resource +where + T: 'static, +{ + /// Creates a new owned resource with the `rep` specified. + /// + /// The returned value is suitable for passing to a guest as either a + /// `(borrow $t)` or `(own $t)`. + pub fn new_own(rep: u32) -> Resource { + Resource { + state: AtomicU32::new(NOT_IN_TABLE), + rep, + _marker: marker::PhantomData, + } + } + + /// Creates a new borrowed resource which isn't actually rooted in any + /// ownership. + /// + /// This can be used to pass to a guest as a borrowed resource and the + /// embedder will know that the `rep` won't be in use by the guest + /// afterwards. Exactly how the lifetime of `rep` works is up to the + /// embedder. + pub fn new_borrow(rep: u32) -> Resource { + Resource { + state: AtomicU32::new(BORROW), + rep, + _marker: marker::PhantomData, + } + } + + /// Returns the underlying 32-bit representation used to originally create + /// this resource. + pub fn rep(&self) -> u32 { + self.rep + } + + /// Returns whether this is an owned resource or not. + /// + /// Owned resources can be safely destroyed by the embedder at any time, and + /// borrowed resources have an owner somewhere else on the stack so can only + /// be accessed, not destroyed. + pub fn owned(&self) -> bool { + match self.state.load(Relaxed) { + BORROW => false, + _ => true, + } + } + + fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { + match ty { + InterfaceType::Own(t) => { + let rep = match self.state.load(Relaxed) { + // If this is a borrow resource then this is a dynamic + // error on behalf of the embedder. + BORROW => { + bail!("cannot lower a `borrow` resource into an `own`") + } + + // If this resource does not yet live in a table then we're + // dynamically transferring ownership to wasm. Record that + // it's no longer present and then pass through the + // representation. + NOT_IN_TABLE => { + let prev = self.state.swap(TAKEN, Relaxed); + assert_eq!(prev, NOT_IN_TABLE); + self.rep + } + + // This resource has already been moved into wasm so this is + // a dynamic error on behalf of the embedder. + TAKEN => bail!("host resource already consumed"), + + // If this resource lives in a host table then try to take + // it out of the table, which may fail, and on success we + // can move the rep into the guest table. + idx => cx.host_resource_lift_own(idx)?, + }; + Ok(cx.guest_resource_lower_own(t, rep)) + } + InterfaceType::Borrow(t) => { + let rep = match self.state.load(Relaxed) { + // If this is already a borrowed resource, nothing else to + // do and the rep is passed through. + BORROW => self.rep, + + // If this resource is already gone, that's a dynamic error + // for the embedder. + TAKEN => bail!("host resource already consumed"), + + // If this resource is not currently in a table then it + // needs to move into a table to participate in state + // related to borrow tracking. Execute the + // `host_resource_lower_own` operation here and update our + // state. + // + // Afterwards this is the same as the `idx` case below. + NOT_IN_TABLE => { + let idx = cx.host_resource_lower_own(self.rep); + let prev = self.state.swap(idx, Relaxed); + assert_eq!(prev, NOT_IN_TABLE); + cx.host_resource_lift_borrow(idx)? + } + + // If this resource lives in a table then it needs to come + // out of the table with borrow-tracking employed. + idx => cx.host_resource_lift_borrow(idx)?, + }; + Ok(cx.guest_resource_lower_borrow(t, rep)) + } + _ => bad_type_info(), + } + } + + fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { + let (state, rep) = match ty { + // Ownership is being transferred from a guest to the host, so move + // it from the guest table into a new `Resource`. Note that this + // isn't immediately inserted into the host table and that's left + // for the future if it's necessary to take a borrow from this owned + // resource. + InterfaceType::Own(t) => { + debug_assert!(cx.resource_type(t) == ResourceType::host::()); + let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?; + assert!(dtor.is_some()); + assert!(flags.is_none()); + (AtomicU32::new(NOT_IN_TABLE), rep) + } + + // The borrow here is lifted from the guest, but note the lack of + // `host_resource_lower_borrow` as it's intentional. Lowering + // a borrow has a special case in the canonical ABI where if the + // receiving module is the owner of the resource then it directly + // receives the `rep` and no other dynamic tracking is employed. + // This effectively mirrors that even though the canonical ABI + // isn't really all that applicable in host context here. + InterfaceType::Borrow(t) => { + debug_assert!(cx.resource_type(t) == ResourceType::host::()); + let rep = cx.guest_resource_lift_borrow(t, index)?; + (AtomicU32::new(BORROW), rep) + } + _ => bad_type_info(), + }; + Ok(Resource { + state, + rep, + _marker: marker::PhantomData, + }) + } +} + +unsafe impl ComponentType for Resource { + const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; + + type Lower = ::Lower; + + fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { + let resource = match ty { + InterfaceType::Own(t) | InterfaceType::Borrow(t) => *t, + other => bail!("expected `own` or `borrow`, found `{}`", desc(other)), + }; + match types.resource_type(resource).kind { + ResourceTypeKind::Host(id) if TypeId::of::() == id => {} + _ => bail!("resource type mismatch"), + } + + Ok(()) + } +} + +unsafe impl Lower for Resource { + fn lower( + &self, + cx: &mut LowerContext<'_, U>, + ty: InterfaceType, + dst: &mut MaybeUninit, + ) -> Result<()> { + self.lower_to_index(cx, ty)? + .lower(cx, InterfaceType::U32, dst) + } + + fn store( + &self, + cx: &mut LowerContext<'_, U>, + ty: InterfaceType, + offset: usize, + ) -> Result<()> { + self.lower_to_index(cx, ty)? + .store(cx, InterfaceType::U32, offset) + } +} + +unsafe impl Lift for Resource { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + let index = u32::lift(cx, InterfaceType::U32, src)?; + Resource::lift_from_index(cx, ty, index) + } + + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + let index = u32::load(cx, InterfaceType::U32, bytes)?; + Resource::lift_from_index(cx, ty, index) + } +} + +impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource").field("rep", &self.rep).finish() + } +} + +/// Representation of a resource in the component model, either a guest-defined +/// or a host-defined resource. +/// +/// This type is similar to [`Resource`] except that it can be used to represent +/// any resource, either host or guest. This type cannot be directly constructed +/// and is only available if the guest returns it to the host (e.g. a function +/// returning a guest-defined resource). This type also does not carry a static +/// type parameter `T` for example and does not have as much information about +/// its type. This means that it's possible to get runtime type-errors when +/// using this type because it cannot statically prevent mismatching resource +/// types. +/// +/// Like [`Resource`] this type represents either an `own` or a `borrow` +/// resource internally. Unlike [`Resource`], however, a [`ResourceAny`] must +/// always be explicitly destroyed with the [`ResourceAny::resource_drop`] +/// method. This will update internal dynamic state tracking and invoke the +/// WebAssembly-defined destructor for a resource, if any. +/// +/// Note that it is required to call `resource_drop` for all instances of +/// [`ResourceAny`]: even borrows. Both borrows and own handles have state +/// associated with them that must be discarded by the time they're done being +/// used. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct ResourceAny { + idx: u32, + ty: ResourceType, + own_state: Option, +} + +#[derive(Copy, Clone)] +struct OwnState { + store: StoreId, + flags: Option, + dtor: Option>, +} + +impl ResourceAny { + /// Returns the corresponding type associated with this resource, either a + /// host-defined type or a guest-defined type. + /// + /// This can be compared against [`ResourceType::host`] for example to see + /// if it's a host-resource or against a type extracted with + /// [`Instance::get_resource`] to see if it's a guest-defined resource. + /// + /// [`Instance::get_resource`]: crate::component::Instance::get_resource + pub fn ty(&self) -> ResourceType { + self.ty + } + + /// Returns whether this is an owned resource, and if not it's a borrowed + /// resource. + pub fn owned(&self) -> bool { + self.own_state.is_some() + } + + /// Destroy this resource and release any state associated with it. + /// + /// This is required to be called (or the async version) for all instances + /// of [`ResourceAny`] to ensure that state associated with this resource is + /// properly cleaned up. For owned resources this may execute the + /// guest-defined destructor if applicable (or the host-defined destructor + /// if one was specified). + pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> { + let mut store = store.as_context_mut(); + assert!( + !store.0.async_support(), + "must use `resource_drop_async` when async support is enabled on the config" + ); + self.resource_drop_impl(&mut store.as_context_mut()) + } + + /// Same as [`ResourceAny::resource_drop`] except for use with async stores + /// to execute the destructor asynchronously. + #[cfg(feature = "async")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + pub async fn resource_drop_async(self, mut store: impl AsContextMut) -> Result<()> + where + T: Send, + { + let mut store = store.as_context_mut(); + assert!( + store.0.async_support(), + "cannot use `resource_drop_async` without enabling async support in the config" + ); + store + .on_fiber(|store| self.resource_drop_impl(store)) + .await? + } + + fn resource_drop_impl(self, store: &mut StoreContextMut<'_, T>) -> Result<()> { + // Attempt to remove `self.idx` from the host table in `store`. + // + // This could fail if the index is invalid or if this is removing an + // `Own` entry which is currently being borrowed. + let rep = host_resource_tables(store.0).resource_drop(None, self.idx)?; + + let (rep, state) = match (rep, &self.own_state) { + (Some(rep), Some(state)) => (rep, state), + + // A `borrow` was removed from the table and no further + // destruction, e.g. the destructor, is required so we're done. + (None, None) => return Ok(()), + + _ => unreachable!(), + }; + + // Double-check that accessing the raw pointers on `state` are safe due + // to the presence of `store` above. + assert_eq!( + store.0.id(), + state.store, + "wrong store used to destroy resource" + ); + + // Implement the reentrance check required by the canonical ABI. Note + // that this happens whether or not a destructor is present. + // + // Note that this should be safe because the raw pointer access in + // `flags` is valid due to `store` being the owner of the flags and + // flags are never destroyed within the store. + if let Some(flags) = state.flags { + unsafe { + if !flags.may_enter() { + bail!(Trap::CannotEnterComponent); + } + } + } + + let dtor = match state.dtor { + Some(dtor) => dtor.as_non_null(), + None => return Ok(()), + }; + let mut args = [ValRaw::u32(rep)]; + + // This should be safe because `dtor` has been checked to belong to the + // `store` provided which means it's valid and still alive. Additionally + // destructors have al been previously type-checked and are guaranteed + // to take one i32 argument and return no results, so the parameters + // here should be configured correctly. + unsafe { crate::Func::call_unchecked_raw(store, dtor, args.as_mut_ptr(), args.len()) } + } + + fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { + match ty { + InterfaceType::Own(t) => { + if cx.resource_type(t) != self.ty { + bail!("mismatched resource types") + } + let rep = cx.host_resource_lift_own(self.idx)?; + Ok(cx.guest_resource_lower_own(t, rep)) + } + InterfaceType::Borrow(t) => { + if cx.resource_type(t) != self.ty { + bail!("mismatched resource types") + } + let rep = cx.host_resource_lift_borrow(self.idx)?; + Ok(cx.guest_resource_lower_borrow(t, rep)) + } + _ => bad_type_info(), + } + } + + fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { + match ty { + InterfaceType::Own(t) => { + let ty = cx.resource_type(t); + let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?; + let idx = cx.host_resource_lower_own(rep); + Ok(ResourceAny { + idx, + ty, + own_state: Some(OwnState { + dtor: dtor.map(SendSyncPtr::new), + flags, + store: cx.store_id(), + }), + }) + } + InterfaceType::Borrow(t) => { + let ty = cx.resource_type(t); + let rep = cx.guest_resource_lift_borrow(t, index)?; + let idx = cx.host_resource_lower_borrow(rep); + Ok(ResourceAny { + idx, + ty, + own_state: None, + }) + } + _ => bad_type_info(), + } + } +} + +unsafe impl ComponentType for ResourceAny { + const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; + + type Lower = ::Lower; + + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { + match ty { + InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()), + other => bail!("expected `own` or `borrow`, found `{}`", desc(other)), + } + } +} + +unsafe impl Lower for ResourceAny { + fn lower( + &self, + cx: &mut LowerContext<'_, T>, + ty: InterfaceType, + dst: &mut MaybeUninit, + ) -> Result<()> { + self.lower_to_index(cx, ty)? + .lower(cx, InterfaceType::U32, dst) + } + + fn store( + &self, + cx: &mut LowerContext<'_, T>, + ty: InterfaceType, + offset: usize, + ) -> Result<()> { + self.lower_to_index(cx, ty)? + .store(cx, InterfaceType::U32, offset) + } +} + +unsafe impl Lift for ResourceAny { + fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + let index = u32::lift(cx, InterfaceType::U32, src)?; + ResourceAny::lift_from_index(cx, ty, index) + } + + fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + let index = u32::load(cx, InterfaceType::U32, bytes)?; + ResourceAny::lift_from_index(cx, ty, index) + } +} + +impl fmt::Debug for OwnState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OwnState") + .field("store", &self.store) + .finish() + } +} + +// This is a loose definition for `Val` primarily so it doesn't need to be +// strictly 100% correct, and equality of resources is a bit iffy anyway, so +// ignore equality here and only factor in the indices and other metadata in +// `ResourceAny`. +impl PartialEq for OwnState { + fn eq(&self, _other: &OwnState) -> bool { + true + } +} + +impl Eq for OwnState {} diff --git a/crates/wasmtime/src/component/types.rs b/crates/wasmtime/src/component/types.rs index 5467187eb089..23d08ba1b600 100644 --- a/crates/wasmtime/src/component/types.rs +++ b/crates/wasmtime/src/component/types.rs @@ -1,5 +1,6 @@ //! This module defines the `Type` type, representing the dynamic form of a component interface type. +use crate::component::matching::InstanceType; use crate::component::values::{self, Val}; use anyhow::{anyhow, Result}; use std::fmt; @@ -7,15 +8,54 @@ use std::mem; use std::ops::Deref; use std::sync::Arc; use wasmtime_environ::component::{ - CanonicalAbiInfo, ComponentTypes, InterfaceType, TypeEnumIndex, TypeFlagsIndex, TypeListIndex, - TypeOptionIndex, TypeRecordIndex, TypeResultIndex, TypeTupleIndex, TypeUnionIndex, - TypeVariantIndex, + CanonicalAbiInfo, ComponentTypes, InterfaceType, ResourceIndex, TypeEnumIndex, TypeFlagsIndex, + TypeListIndex, TypeOptionIndex, TypeRecordIndex, TypeResultIndex, TypeTupleIndex, + TypeUnionIndex, TypeVariantIndex, }; - +use wasmtime_environ::PrimaryMap; + +pub use crate::component::resources::ResourceType; + +/// An owned and `'static` handle for type information in a component. +/// +/// The components here are: +/// +/// * `index` - a `TypeFooIndex` defined in the `wasmtime_environ` crate. This +/// then points into the next field of... +/// +/// * `types` - this is an allocation originally created from compilation and is +/// stored in a compiled `Component`. This contains all types necessary and +/// information about recursive structures and all other type information +/// within the component. The above `index` points into this structure. +/// +/// * `resources` - this is used to "close the loop" and represent a concrete +/// instance type rather than an abstract component type. Instantiating a +/// component with different resources produces different instance types but +/// the same underlying component type, so this field serves the purpose to +/// distinguish instance types from one another. This is runtime state created +/// during instantiation and threaded through here. #[derive(Clone)] struct Handle { index: T, types: Arc, + resources: Arc>, +} + +impl Handle { + fn new(index: T, ty: &InstanceType<'_>) -> Handle { + Handle { + index, + types: ty.types.clone(), + resources: ty.resources.clone(), + } + } + + fn instance(&self) -> InstanceType<'_> { + InstanceType { + types: &self.types, + resources: &self.resources, + } + } } impl fmt::Debug for Handle { @@ -31,7 +71,9 @@ impl PartialEq for Handle { // FIXME: This is an overly-restrictive definition of equality in that it doesn't consider types to be // equal unless they refer to the same declaration in the same component. It's a good shortcut for the // common case, but we should also do a recursive structural equality test if the shortcut test fails. - self.index == other.index && Arc::ptr_eq(&self.types, &other.types) + self.index == other.index + && Arc::ptr_eq(&self.types, &other.types) + && Arc::ptr_eq(&self.resources, &other.resources) } } @@ -47,16 +89,13 @@ impl List { Ok(Val::List(values::List::new(self, values)?)) } - pub(crate) fn from(index: TypeListIndex, types: &Arc) -> Self { - List(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeListIndex, ty: &InstanceType<'_>) -> Self { + List(Handle::new(index, ty)) } /// Retreive the element type of this `list`. pub fn ty(&self) -> Type { - Type::from(&self.0.types[self.0.index].element, &self.0.types) + Type::from(&self.0.types[self.0.index].element, &self.0.instance()) } } @@ -78,18 +117,15 @@ impl Record { Ok(Val::Record(values::Record::new(self, values)?)) } - pub(crate) fn from(index: TypeRecordIndex, types: &Arc) -> Self { - Record(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeRecordIndex, ty: &InstanceType<'_>) -> Self { + Record(Handle::new(index, ty)) } /// Retrieve the fields of this `record` in declaration order. pub fn fields(&self) -> impl ExactSizeIterator> { self.0.types[self.0.index].fields.iter().map(|field| Field { name: &field.name, - ty: Type::from(&field.ty, &self.0.types), + ty: Type::from(&field.ty, &self.0.instance()), }) } } @@ -104,11 +140,8 @@ impl Tuple { Ok(Val::Tuple(values::Tuple::new(self, values)?)) } - pub(crate) fn from(index: TypeTupleIndex, types: &Arc) -> Self { - Tuple(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeTupleIndex, ty: &InstanceType<'_>) -> Self { + Tuple(Handle::new(index, ty)) } /// Retrieve the types of the fields of this `tuple` in declaration order. @@ -116,7 +149,7 @@ impl Tuple { self.0.types[self.0.index] .types .iter() - .map(|ty| Type::from(ty, &self.0.types)) + .map(|ty| Type::from(ty, &self.0.instance())) } } @@ -138,18 +171,18 @@ impl Variant { Ok(Val::Variant(values::Variant::new(self, name, value)?)) } - pub(crate) fn from(index: TypeVariantIndex, types: &Arc) -> Self { - Variant(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeVariantIndex, ty: &InstanceType<'_>) -> Self { + Variant(Handle::new(index, ty)) } /// Retrieve the cases of this `variant` in declaration order. pub fn cases(&self) -> impl ExactSizeIterator { self.0.types[self.0.index].cases.iter().map(|case| Case { name: &case.name, - ty: case.ty.as_ref().map(|ty| Type::from(ty, &self.0.types)), + ty: case + .ty + .as_ref() + .map(|ty| Type::from(ty, &self.0.instance())), }) } } @@ -164,11 +197,8 @@ impl Enum { Ok(Val::Enum(values::Enum::new(self, name)?)) } - pub(crate) fn from(index: TypeEnumIndex, types: &Arc) -> Self { - Enum(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeEnumIndex, ty: &InstanceType<'_>) -> Self { + Enum(Handle::new(index, ty)) } /// Retrieve the names of the cases of this `enum` in declaration order. @@ -190,11 +220,8 @@ impl Union { Ok(Val::Union(values::Union::new(self, discriminant, value)?)) } - pub(crate) fn from(index: TypeUnionIndex, types: &Arc) -> Self { - Union(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeUnionIndex, ty: &InstanceType<'_>) -> Self { + Union(Handle::new(index, ty)) } /// Retrieve the types of the cases of this `union` in declaration order. @@ -202,7 +229,7 @@ impl Union { self.0.types[self.0.index] .types .iter() - .map(|ty| Type::from(ty, &self.0.types)) + .map(|ty| Type::from(ty, &self.0.instance())) } } @@ -216,16 +243,13 @@ impl OptionType { Ok(Val::Option(values::OptionVal::new(self, value)?)) } - pub(crate) fn from(index: TypeOptionIndex, types: &Arc) -> Self { - OptionType(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeOptionIndex, ty: &InstanceType<'_>) -> Self { + OptionType(Handle::new(index, ty)) } /// Retrieve the type parameter for this `option`. pub fn ty(&self) -> Type { - Type::from(&self.0.types[self.0.index].ty, &self.0.types) + Type::from(&self.0.types[self.0.index].ty, &self.0.instance()) } } @@ -239,18 +263,15 @@ impl ResultType { Ok(Val::Result(values::ResultVal::new(self, value)?)) } - pub(crate) fn from(index: TypeResultIndex, types: &Arc) -> Self { - ResultType(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeResultIndex, ty: &InstanceType<'_>) -> Self { + ResultType(Handle::new(index, ty)) } /// Retrieve the `ok` type parameter for this `option`. pub fn ok(&self) -> Option { Some(Type::from( self.0.types[self.0.index].ok.as_ref()?, - &self.0.types, + &self.0.instance(), )) } @@ -258,7 +279,7 @@ impl ResultType { pub fn err(&self) -> Option { Some(Type::from( self.0.types[self.0.index].err.as_ref()?, - &self.0.types, + &self.0.instance(), )) } } @@ -273,11 +294,8 @@ impl Flags { Ok(Val::Flags(values::Flags::new(self, names)?)) } - pub(crate) fn from(index: TypeFlagsIndex, types: &Arc) -> Self { - Flags(Handle { - index, - types: types.clone(), - }) + pub(crate) fn from(index: TypeFlagsIndex, ty: &InstanceType<'_>) -> Self { + Flags(Handle::new(index, ty)) } /// Retrieve the names of the flags of this `flags` type in declaration order. @@ -319,6 +337,8 @@ pub enum Type { Option(OptionType), Result(ResultType), Flags(Flags), + Own(ResourceType), + Borrow(ResourceType), } impl Type { @@ -439,6 +459,30 @@ impl Type { } } + /// Retrieve the inner [`ResourceType`] of a [`Type::Own`]. + /// + /// # Panics + /// + /// This will panic if `self` is not a [`Type::Own`]. + pub fn unwrap_own(&self) -> &ResourceType { + match self { + Type::Own(ty) => ty, + _ => panic!("attempted to unwrap a {} as a own", self.desc()), + } + } + + /// Retrieve the inner [`ResourceType`] of a [`Type::Borrow`]. + /// + /// # Panics + /// + /// This will panic if `self` is not a [`Type::Borrow`]. + pub fn unwrap_borrow(&self) -> &ResourceType { + match self { + Type::Borrow(ty) => ty, + _ => panic!("attempted to unwrap a {} as a own", self.desc()), + } + } + pub(crate) fn check(&self, value: &Val) -> Result<()> { let other = &value.ty(); if self == other { @@ -458,7 +502,7 @@ impl Type { } /// Convert the specified `InterfaceType` to a `Type`. - pub(crate) fn from(ty: &InterfaceType, types: &Arc) -> Self { + pub(crate) fn from(ty: &InterfaceType, instance: &InstanceType<'_>) -> Self { match ty { InterfaceType::Bool => Type::Bool, InterfaceType::S8 => Type::S8, @@ -473,15 +517,17 @@ impl Type { InterfaceType::Float64 => Type::Float64, InterfaceType::Char => Type::Char, InterfaceType::String => Type::String, - InterfaceType::List(index) => Type::List(List::from(*index, types)), - InterfaceType::Record(index) => Type::Record(Record::from(*index, types)), - InterfaceType::Tuple(index) => Type::Tuple(Tuple::from(*index, types)), - InterfaceType::Variant(index) => Type::Variant(Variant::from(*index, types)), - InterfaceType::Enum(index) => Type::Enum(Enum::from(*index, types)), - InterfaceType::Union(index) => Type::Union(Union::from(*index, types)), - InterfaceType::Option(index) => Type::Option(OptionType::from(*index, types)), - InterfaceType::Result(index) => Type::Result(ResultType::from(*index, types)), - InterfaceType::Flags(index) => Type::Flags(Flags::from(*index, types)), + InterfaceType::List(index) => Type::List(List::from(*index, instance)), + InterfaceType::Record(index) => Type::Record(Record::from(*index, instance)), + InterfaceType::Tuple(index) => Type::Tuple(Tuple::from(*index, instance)), + InterfaceType::Variant(index) => Type::Variant(Variant::from(*index, instance)), + InterfaceType::Enum(index) => Type::Enum(Enum::from(*index, instance)), + InterfaceType::Union(index) => Type::Union(Union::from(*index, instance)), + InterfaceType::Option(index) => Type::Option(OptionType::from(*index, instance)), + InterfaceType::Result(index) => Type::Result(ResultType::from(*index, instance)), + InterfaceType::Flags(index) => Type::Flags(Flags::from(*index, instance)), + InterfaceType::Own(index) => Type::Own(instance.resource_type(*index)), + InterfaceType::Borrow(index) => Type::Borrow(instance.resource_type(*index)), } } @@ -509,6 +555,8 @@ impl Type { Type::Option(_) => "option", Type::Result(_) => "result", Type::Flags(_) => "flags", + Type::Own(_) => "own", + Type::Borrow(_) => "borrow", } } } diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index e98eb7566bd3..f40a8f473947 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -1,5 +1,6 @@ use crate::component::func::{bad_type_info, Lift, LiftContext, Lower, LowerContext}; use crate::component::types::{self, Type}; +use crate::component::ResourceAny; use crate::ValRaw; use anyhow::{anyhow, bail, Context, Error, Result}; use std::collections::HashMap; @@ -591,7 +592,45 @@ impl fmt::Debug for Flags { } } -/// Represents possible runtime values which a component function can either consume or produce +/// Represents possible runtime values which a component function can either +/// consume or produce +/// +/// This is a dynamic representation of possible values in the component model. +/// Note that this is not an efficient representation but is instead intended to +/// be a flexible and somewhat convenient representation. The most efficient +/// representation of component model types is to use the `bindgen!` macro to +/// generate native Rust types with specialized liftings and lowerings. +/// +/// This type is used in conjunction with [`Func::call`] for example if the +/// signature of a component is not statically known ahead of time. +/// +/// # Notes on Equality +/// +/// This type implements both the Rust `PartialEq` and `Eq` traits. This type +/// additionally contains values which are not necessarily easily equated, +/// however, such as floats (`Float32` and `Float64`) and resources. Equality +/// does require that two values have the same type, and then these cases are +/// handled as: +/// +/// * Floats are tested if they are "semantically the same" meaning all NaN +/// values are equal to all other NaN values. Additionally zero values must be +/// exactly the same, so positive zero is not equal to negative zero. The +/// primary use case at this time is fuzzing-related equality which this is +/// sufficient for. +/// +/// * Resources are tested if their types and indices into the host table are +/// equal. This does not compare the underlying representation so borrows of +/// the same guest resource are not considered equal. This additionally +/// doesn't go further and test for equality in the guest itself (for example +/// two different heap allocations of `Box` can be equal in normal Rust +/// if they contain the same value, but will never be considered equal when +/// compared as `Val::Resource`s). +/// +/// In general if a strict guarantee about equality is required here it's +/// recommended to "build your own" as this equality intended for fuzzing +/// Wasmtime may not be suitable for you. +/// +/// [`Func::call`]: crate::component::Func::call #[derive(Debug, Clone)] #[allow(missing_docs)] pub enum Val { @@ -617,6 +656,7 @@ pub enum Val { Option(OptionVal), Result(ResultVal), Flags(Flags), + Resource(ResourceAny), } impl Val { @@ -645,12 +685,19 @@ impl Val { Val::Option(OptionVal { ty, .. }) => Type::Option(ty.clone()), Val::Result(ResultVal { ty, .. }) => Type::Result(ty.clone()), Val::Flags(Flags { ty, .. }) => Type::Flags(ty.clone()), + Val::Resource(r) => { + if r.owned() { + Type::Own(r.ty()) + } else { + Type::Borrow(r.ty()) + } + } } } /// Deserialize a value of this type from core Wasm stack values. pub(crate) fn lift( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, ty: InterfaceType, src: &mut std::slice::Iter<'_, ValRaw>, ) -> Result { @@ -667,6 +714,9 @@ impl Val { InterfaceType::Float32 => Val::Float32(f32::lift(cx, ty, next(src))?), InterfaceType::Float64 => Val::Float64(f64::lift(cx, ty, next(src))?), InterfaceType::Char => Val::Char(char::lift(cx, ty, next(src))?), + InterfaceType::Own(_) | InterfaceType::Borrow(_) => { + Val::Resource(ResourceAny::lift(cx, ty, next(src))?) + } InterfaceType::String => { Val::String(Box::::lift(cx, ty, &[*next(src), *next(src)])?) } @@ -677,7 +727,7 @@ impl Val { load_list(cx, i, ptr, len)? } InterfaceType::Record(i) => Val::Record(Record { - ty: types::Record::from(i, cx.types), + ty: types::Record::from(i, &cx.instance_type()), values: cx.types[i] .fields .iter() @@ -685,7 +735,7 @@ impl Val { .collect::>()?, }), InterfaceType::Tuple(i) => Val::Tuple(Tuple { - ty: types::Tuple::from(i, cx.types), + ty: types::Tuple::from(i, &cx.instance_type()), values: cx.types[i] .types .iter() @@ -701,7 +751,7 @@ impl Val { )?; Val::Variant(Variant { - ty: types::Variant::from(i, cx.types), + ty: types::Variant::from(i, &cx.instance_type()), discriminant, value, }) @@ -715,7 +765,7 @@ impl Val { )?; Val::Enum(Enum { - ty: types::Enum::from(i, cx.types), + ty: types::Enum::from(i, &cx.instance_type()), discriminant, }) } @@ -728,7 +778,7 @@ impl Val { )?; Val::Union(Union { - ty: types::Union::from(i, cx.types), + ty: types::Union::from(i, &cx.instance_type()), discriminant, value, }) @@ -742,7 +792,7 @@ impl Val { )?; Val::Option(OptionVal { - ty: types::OptionType::from(i, cx.types), + ty: types::OptionType::from(i, &cx.instance_type()), discriminant, value, }) @@ -757,7 +807,7 @@ impl Val { )?; Val::Result(ResultVal { - ty: types::ResultType::from(i, cx.types), + ty: types::ResultType::from(i, &cx.instance_type()), discriminant, value, }) @@ -770,7 +820,7 @@ impl Val { .collect::>()?; Val::Flags(Flags { - ty: types::Flags::from(i, cx.types), + ty: types::Flags::from(i, &cx.instance_type()), count, value, }) @@ -779,7 +829,7 @@ impl Val { } /// Deserialize a value of this type from the heap. - pub(crate) fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + pub(crate) fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { Ok(match ty { InterfaceType::Bool => Val::Bool(bool::load(cx, ty, bytes)?), InterfaceType::S8 => Val::S8(i8::load(cx, ty, bytes)?), @@ -794,6 +844,9 @@ impl Val { InterfaceType::Float64 => Val::Float64(f64::load(cx, ty, bytes)?), InterfaceType::Char => Val::Char(char::load(cx, ty, bytes)?), InterfaceType::String => Val::String(>::load(cx, ty, bytes)?), + InterfaceType::Own(_) | InterfaceType::Borrow(_) => { + Val::Resource(ResourceAny::load(cx, ty, bytes)?) + } InterfaceType::List(i) => { // FIXME: needs memory64 treatment let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as usize; @@ -802,11 +855,11 @@ impl Val { } InterfaceType::Record(i) => Val::Record(Record { - ty: types::Record::from(i, cx.types), + ty: types::Record::from(i, &cx.instance_type()), values: load_record(cx, cx.types[i].fields.iter().map(|field| field.ty), bytes)?, }), InterfaceType::Tuple(i) => Val::Tuple(Tuple { - ty: types::Tuple::from(i, cx.types), + ty: types::Tuple::from(i, &cx.instance_type()), values: load_record(cx, cx.types[i].types.iter().copied(), bytes)?, }), InterfaceType::Variant(i) => { @@ -815,7 +868,7 @@ impl Val { load_variant(cx, &ty.info, ty.cases.iter().map(|case| case.ty), bytes)?; Val::Variant(Variant { - ty: types::Variant::from(i, cx.types), + ty: types::Variant::from(i, &cx.instance_type()), discriminant, value, }) @@ -826,7 +879,7 @@ impl Val { load_variant(cx, &ty.info, ty.names.iter().map(|_| None), bytes)?; Val::Enum(Enum { - ty: types::Enum::from(i, cx.types), + ty: types::Enum::from(i, &cx.instance_type()), discriminant, }) } @@ -836,7 +889,7 @@ impl Val { load_variant(cx, &ty.info, ty.types.iter().copied().map(Some), bytes)?; Val::Union(Union { - ty: types::Union::from(i, cx.types), + ty: types::Union::from(i, &cx.instance_type()), discriminant, value, }) @@ -847,7 +900,7 @@ impl Val { load_variant(cx, &ty.info, [None, Some(ty.ty)].into_iter(), bytes)?; Val::Option(OptionVal { - ty: types::OptionType::from(i, cx.types), + ty: types::OptionType::from(i, &cx.instance_type()), discriminant, value, }) @@ -858,13 +911,13 @@ impl Val { load_variant(cx, &ty.info, [ty.ok, ty.err].into_iter(), bytes)?; Val::Result(ResultVal { - ty: types::ResultType::from(i, cx.types), + ty: types::ResultType::from(i, &cx.instance_type()), discriminant, value, }) } InterfaceType::Flags(i) => Val::Flags(Flags { - ty: types::Flags::from(i, cx.types), + ty: types::Flags::from(i, &cx.instance_type()), count: u32::try_from(cx.types[i].names.len())?, value: match FlagsSize::from_count(cx.types[i].names.len()) { FlagsSize::Size0 => Box::new([]), @@ -908,6 +961,7 @@ impl Val { Val::Float32(value) => value.lower(cx, ty, next_mut(dst))?, Val::Float64(value) => value.lower(cx, ty, next_mut(dst))?, Val::Char(value) => value.lower(cx, ty, next_mut(dst))?, + Val::Resource(value) => value.lower(cx, ty, next_mut(dst))?, Val::String(value) => { let my_dst = &mut MaybeUninit::<[ValRaw; 2]>::uninit(); value.lower(cx, ty, my_dst)?; @@ -982,6 +1036,7 @@ impl Val { Val::Float64(value) => value.store(cx, ty, offset)?, Val::Char(value) => value.store(cx, ty, offset)?, Val::String(value) => value.store(cx, ty, offset)?, + Val::Resource(value) => value.store(cx, ty, offset)?, Val::List(List { values, .. }) => { let ty = match ty { InterfaceType::List(i) => &cx.types[i], @@ -1118,6 +1173,8 @@ impl PartialEq for Val { (Self::Result(_), _) => false, (Self::Flags(l), Self::Flags(r)) => l == r, (Self::Flags(_), _) => false, + (Self::Resource(l), Self::Resource(r)) => l == r, + (Self::Resource(_), _) => false, } } } @@ -1180,7 +1237,7 @@ impl GenericVariant<'_> { } } -fn load_list(cx: &LiftContext<'_>, ty: TypeListIndex, ptr: usize, len: usize) -> Result { +fn load_list(cx: &mut LiftContext<'_>, ty: TypeListIndex, ptr: usize, len: usize) -> Result { let elem = cx.types[ty].element; let abi = cx.types.canonical_abi(&elem); let element_size = usize::try_from(abi.size32).unwrap(); @@ -1198,7 +1255,7 @@ fn load_list(cx: &LiftContext<'_>, ty: TypeListIndex, ptr: usize, len: usize) -> } Ok(Val::List(List { - ty: types::List::from(ty, cx.types), + ty: types::List::from(ty, &cx.instance_type()), values: (0..len) .map(|index| { Val::load( @@ -1212,7 +1269,7 @@ fn load_list(cx: &LiftContext<'_>, ty: TypeListIndex, ptr: usize, len: usize) -> } fn load_record( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, types: impl Iterator, bytes: &[u8], ) -> Result> { @@ -1229,7 +1286,7 @@ fn load_record( } fn load_variant( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, info: &VariantInfo, mut types: impl ExactSizeIterator>, bytes: &[u8], @@ -1263,7 +1320,7 @@ fn load_variant( } fn lift_variant( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, flatten_count: usize, mut types: impl ExactSizeIterator>, src: &mut std::slice::Iter<'_, ValRaw>, diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index df3f6d0231f7..cbd124e8fdae 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -338,6 +338,14 @@ pub struct StoreOpaque { /// Note that this is `ManuallyDrop` as it must be dropped after /// `store_data` above, where the function pointers are stored. rooted_host_funcs: ManuallyDrop>>, + + /// Runtime state for components used in the handling of resources, borrow, + /// and calls. These also interact with the `ResourceAny` type and its + /// internal representation. + #[cfg(feature = "component-model")] + component_host_table: wasmtime_runtime::component::ResourceTable, + #[cfg(feature = "component-model")] + component_calls: wasmtime_runtime::component::CallContexts, } #[cfg(feature = "async")] @@ -474,6 +482,10 @@ impl Store { hostcall_val_storage: Vec::new(), wasm_val_raw_storage: Vec::new(), rooted_host_funcs: ManuallyDrop::new(Vec::new()), + #[cfg(feature = "component-model")] + component_host_table: Default::default(), + #[cfg(feature = "component-model")] + component_calls: Default::default(), }, limiter: None, call_hook: None, @@ -1536,6 +1548,16 @@ at https://bytecodealliance.org/security. ); std::process::abort(); } + + #[cfg(feature = "component-model")] + pub(crate) fn component_calls_and_host_table( + &mut self, + ) -> ( + &mut wasmtime_runtime::component::CallContexts, + &mut wasmtime_runtime::component::ResourceTable, + ) { + (&mut self.component_calls, &mut self.component_host_table) + } } impl StoreContextMut<'_, T> { @@ -2047,6 +2069,11 @@ unsafe impl wasmtime_runtime::Store for StoreInner { self.epoch_deadline_behavior = behavior; delta_result } + + #[cfg(feature = "component-model")] + fn component_calls(&mut self) -> &mut wasmtime_runtime::component::CallContexts { + &mut self.component_calls + } } impl StoreInner { diff --git a/crates/wasmtime/src/store/data.rs b/crates/wasmtime/src/store/data.rs index 3d36f8420554..c45592a0bfde 100644 --- a/crates/wasmtime/src/store/data.rs +++ b/crates/wasmtime/src/store/data.rs @@ -187,7 +187,7 @@ where /// owned by a `Store` and will embed a `StoreId` internally to say which store /// it came from. Comparisons with this value are how panics are generated for /// mismatching the item that a store belongs to. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct StoreId(NonZeroU64); impl StoreId { diff --git a/crates/wast/src/component.rs b/crates/wast/src/component.rs index 425689de49ca..4975009821d7 100644 --- a/crates/wast/src/component.rs +++ b/crates/wast/src/component.rs @@ -376,6 +376,7 @@ fn mismatch(expected: &WastVal<'_>, actual: &Val) -> Result<()> { Val::Option(..) => "option", Val::Result(..) => "result", Val::Flags(..) => "flags", + Val::Resource(..) => "resource", }; bail!("expected `{expected}` got `{actual}`") } diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index b699d24aa101..6e340aef2daf 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -57,6 +57,10 @@ pub fn link_spectest( #[cfg(feature = "component-model")] pub fn link_component_spectest(linker: &mut component::Linker) -> Result<()> { + use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; + use std::sync::Arc; + use wasmtime::component::Resource; + let engine = linker.engine().clone(); linker .root() @@ -76,5 +80,75 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( "#, )?; i.module("simple-module", &module)?; + + struct Resource1; + struct Resource2; + + #[derive(Default)] + struct ResourceState { + drops: AtomicU32, + last_drop: AtomicU32, + } + + let state = Arc::new(ResourceState::default()); + + i.resource::("resource1", { + let state = state.clone(); + move |_, rep| { + state.drops.fetch_add(1, SeqCst); + state.last_drop.store(rep, SeqCst); + } + })?; + i.resource::("resource2", |_, _| {})?; + // Currently the embedder API requires redefining the resource destructor + // here despite this being the same type as before, and fixing that is left + // for a future refactoring. + i.resource::("resource1-again", |_, _| { + panic!("shouldn't be destroyed"); + })?; + + i.func_wrap("[constructor]resource1", |_cx, (rep,): (u32,)| { + Ok((Resource::::new_own(rep),)) + })?; + i.func_wrap( + "[static]resource1.assert", + |_cx, (resource, rep): (Resource, u32)| { + assert_eq!(resource.rep(), rep); + Ok(()) + }, + )?; + i.func_wrap("[static]resource1.last-drop", { + let state = state.clone(); + move |_, (): ()| Ok((state.last_drop.load(SeqCst),)) + })?; + i.func_wrap("[static]resource1.drops", { + let state = state.clone(); + move |_, (): ()| Ok((state.drops.load(SeqCst),)) + })?; + i.func_wrap( + "[method]resource1.simple", + |_cx, (resource, rep): (Resource, u32)| { + assert!(!resource.owned()); + assert_eq!(resource.rep(), rep); + Ok(()) + }, + )?; + + i.func_wrap( + "[method]resource1.take-borrow", + |_, (a, b): (Resource, Resource)| { + assert!(!a.owned()); + assert!(!b.owned()); + Ok(()) + }, + )?; + i.func_wrap( + "[method]resource1.take-own", + |_cx, (a, b): (Resource, Resource)| { + assert!(!a.owned()); + assert!(b.owned()); + Ok(()) + }, + )?; Ok(()) } diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs index d57c412e98b5..3137038a6aac 100644 --- a/crates/winch/src/compiler.rs +++ b/crates/winch/src/compiler.rs @@ -127,7 +127,6 @@ impl wasmtime_environ::Compiler for Compiler { fn compile_wasm_to_native_trampoline( &self, - _translation: &ModuleTranslation<'_>, wasm_func_ty: &wasmtime_environ::WasmFuncType, ) -> Result, CompileError> { let buffer = self diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index 356fd83a3d31..ff2ca10a437b 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -681,8 +681,8 @@ impl<'a> InterfaceGenerator<'a> { TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs), TypeDefKind::Future(_) => todo!("generate for future"), TypeDefKind::Stream(_) => todo!("generate for stream"), - TypeDefKind::Handle(_) => todo!(), - TypeDefKind::Resource => todo!(), + TypeDefKind::Handle(_) => todo!("#6722"), + TypeDefKind::Resource => todo!("#6722"), TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-bindgen/src/rust.rs b/crates/wit-bindgen/src/rust.rs index 94cc233f2f75..38f7f89c1bfd 100644 --- a/crates/wit-bindgen/src/rust.rs +++ b/crates/wit-bindgen/src/rust.rs @@ -185,8 +185,8 @@ pub trait RustGenerator<'a> { self.push_str(">"); } - TypeDefKind::Handle(_) => todo!(), - TypeDefKind::Resource => todo!(), + TypeDefKind::Handle(_) => todo!("#6722"), + TypeDefKind::Resource => todo!("#6722"), TypeDefKind::Type(t) => self.print_ty(t, mode), TypeDefKind::Unknown => unreachable!(), @@ -297,8 +297,8 @@ pub trait RustGenerator<'a> { TypeDefKind::Variant(_) => out.push_str("Variant"), TypeDefKind::Enum(_) => out.push_str("Enum"), TypeDefKind::Union(_) => out.push_str("Union"), - TypeDefKind::Handle(_) => todo!(), - TypeDefKind::Resource => todo!(), + TypeDefKind::Handle(_) => todo!("#6722"), + TypeDefKind::Resource => todo!("#6722"), TypeDefKind::Unknown => unreachable!(), }, } diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs index 07fe7c868ba2..7e5e21f90cf3 100644 --- a/tests/all/component_model.rs +++ b/tests/all/component_model.rs @@ -15,6 +15,7 @@ mod instance; mod macros; mod nested; mod post_return; +mod resources; mod strings; #[test] diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 85bc25fc2001..3a3bfe8e3899 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -777,11 +777,14 @@ fn strings() -> Result<()> { list16_to_str.post_return(&mut store)?; let ret = str_to_list8.call(&mut store, (x,))?.0; - assert_eq!(ret.iter(&store).collect::>>()?, x.as_bytes()); + assert_eq!( + ret.iter(&mut store).collect::>>()?, + x.as_bytes() + ); str_to_list8.post_return(&mut store)?; let ret = str_to_list16.call(&mut store, (x,))?.0; - assert_eq!(ret.iter(&store).collect::>>()?, utf16,); + assert_eq!(ret.iter(&mut store).collect::>>()?, utf16,); str_to_list16.post_return(&mut store)?; Ok(()) @@ -2381,11 +2384,10 @@ fn errors_that_poison_instance() -> Result<()> { Ok(_) => panic!("expected an error"), Err(e) => e, }; - assert!( - err.to_string() - .contains("cannot reenter component instance"), - "{}", - err, + assert_eq!( + err.downcast_ref::(), + Some(&Trap::CannotEnterComponent), + "{err}", ); } } diff --git a/tests/all/component_model/import.rs b/tests/all/component_model/import.rs index 705348e9dfa7..38e23edd4f95 100644 --- a/tests/all/component_model/import.rs +++ b/tests/all/component_model/import.rs @@ -4,7 +4,7 @@ use super::REALLOC_AND_FREE; use anyhow::Result; use std::ops::Deref; use wasmtime::component::*; -use wasmtime::{Store, StoreContextMut, WasmBacktrace}; +use wasmtime::{Store, StoreContextMut, Trap, WasmBacktrace}; #[test] fn can_compile() -> Result<()> { @@ -446,8 +446,9 @@ fn attempt_to_reenter_during_host() -> Result<()> { |mut store: StoreContextMut<'_, StaticState>, _: ()| -> Result<()> { let func = store.data_mut().func.take().unwrap(); let trap = func.call(&mut store, ()).unwrap_err(); - assert!( - format!("{trap:?}").contains("cannot reenter component instance"), + assert_eq!( + trap.downcast_ref(), + Some(&Trap::CannotEnterComponent), "bad trap: {trap:?}", ); Ok(()) @@ -472,8 +473,9 @@ fn attempt_to_reenter_during_host() -> Result<()> { |mut store: StoreContextMut<'_, DynamicState>, _, _| { let func = store.data_mut().func.take().unwrap(); let trap = func.call(&mut store, &[], &mut []).unwrap_err(); - assert!( - format!("{trap:?}").contains("cannot reenter component instance"), + assert_eq!( + trap.downcast_ref(), + Some(&Trap::CannotEnterComponent), "bad trap: {trap:?}", ); Ok(()) diff --git a/tests/all/component_model/post_return.rs b/tests/all/component_model/post_return.rs index 8cd5d934a9b0..ffdbdf931f6f 100644 --- a/tests/all/component_model/post_return.rs +++ b/tests/all/component_model/post_return.rs @@ -40,18 +40,16 @@ fn invalid_api() -> Result<()> { // Ensure that we can't reenter the instance through either this function or // another one. let err = thunk1.call(&mut store, ()).unwrap_err(); - assert!( - err.to_string() - .contains("cannot reenter component instance"), - "{}", - err + assert_eq!( + err.downcast_ref(), + Some(&Trap::CannotEnterComponent), + "{err}", ); let err = thunk2.call(&mut store, ()).unwrap_err(); - assert!( - err.to_string() - .contains("cannot reenter component instance"), - "{}", - err + assert_eq!( + err.downcast_ref(), + Some(&Trap::CannotEnterComponent), + "{err}", ); // Calling post-return on the wrong function should panic @@ -288,11 +286,10 @@ fn trap_in_post_return_poisons_instance() -> Result<()> { let trap = f.post_return(&mut store).unwrap_err().downcast::()?; assert_eq!(trap, Trap::UnreachableCodeReached); let err = f.call(&mut store, ()).unwrap_err(); - assert!( - err.to_string() - .contains("cannot reenter component instance"), - "{}", - err + assert_eq!( + err.downcast_ref(), + Some(&Trap::CannotEnterComponent), + "{err}", ); assert_panics( || drop(f.post_return(&mut store)), diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs new file mode 100644 index 000000000000..e8dd9de4dd4b --- /dev/null +++ b/tests/all/component_model/resources.rs @@ -0,0 +1,1284 @@ +#![cfg(not(miri))] + +use anyhow::Result; +use wasmtime::component::*; +use wasmtime::{Store, Trap}; + +#[test] +fn host_resource_types() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + (import "u" (type $u (sub resource))) + + (export "t1" (type $t)) + (export "t2" (type $t)) + (export "u1" (type $u)) + (export "u2" (type $u)) + + (component $c + (import "r" (type $r (sub resource))) + (export "r1" (type $r)) + ) + (instance $i1 (instantiate $c (with "r" (type $t)))) + (instance $i2 (instantiate $c (with "r" (type $t)))) + (export "t3" (type $i1 "r1")) + (export "t4" (type $i2 "r1")) + ) + "#, + )?; + + struct T; + struct U; + assert!(ResourceType::host::() != ResourceType::host::()); + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + linker.root().resource::("u", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + let t1 = i.get_resource(&mut store, "t1").unwrap(); + let t2 = i.get_resource(&mut store, "t2").unwrap(); + let t3 = i.get_resource(&mut store, "t3").unwrap(); + let t4 = i.get_resource(&mut store, "t4").unwrap(); + let u1 = i.get_resource(&mut store, "u1").unwrap(); + let u2 = i.get_resource(&mut store, "u2").unwrap(); + + assert_eq!(t1, ResourceType::host::()); + assert_eq!(t2, ResourceType::host::()); + assert_eq!(t3, ResourceType::host::()); + assert_eq!(t4, ResourceType::host::()); + assert_eq!(u1, ResourceType::host::()); + assert_eq!(u2, ResourceType::host::()); + Ok(()) +} + +#[test] +fn guest_resource_types() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t (resource (rep i32))) + (type $u (resource (rep i32))) + + (export "t1" (type $t)) + (export "t2" (type $t)) + (export "u1" (type $u)) + (export "u2" (type $u)) + + (component $c + (import "r" (type $r (sub resource))) + (export "r1" (type $r)) + ) + (instance $i1 (instantiate $c (with "r" (type $t)))) + (instance $i2 (instantiate $c (with "r" (type $t)))) + (export "t3" (type $i1 "r1")) + (export "t4" (type $i2 "r1")) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let i = linker.instantiate(&mut store, &c)?; + let t1 = i.get_resource(&mut store, "t1").unwrap(); + let t2 = i.get_resource(&mut store, "t2").unwrap(); + let t3 = i.get_resource(&mut store, "t3").unwrap(); + let t4 = i.get_resource(&mut store, "t4").unwrap(); + let u1 = i.get_resource(&mut store, "u1").unwrap(); + let u2 = i.get_resource(&mut store, "u2").unwrap(); + + assert_ne!(t1, u1); + assert_eq!(t1, t2); + assert_eq!(t1, t3); + assert_eq!(t1, t4); + assert_eq!(u1, u2); + Ok(()) +} + +#[test] +fn resource_any() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + (type $u' (resource (rep i32))) + + (export $t "t" (type $t')) + (export $u "u" (type $u')) + + (core func $t_ctor (canon resource.new $t)) + (core func $u_ctor (canon resource.new $u)) + + (func (export "[constructor]t") (param "x" u32) (result (own $t)) + (canon lift (core func $t_ctor))) + (func (export "[constructor]u") (param "x" u32) (result (own $u)) + (canon lift (core func $u_ctor))) + + (core func $t_drop (canon resource.drop $t)) + (core func $u_drop (canon resource.drop $u)) + + (func (export "drop-t") (param "x" (own $t)) + (canon lift (core func $t_drop))) + (func (export "drop-u") (param "x" (own $u)) + (canon lift (core func $u_drop))) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let i = linker.instantiate(&mut store, &c)?; + let t = i.get_resource(&mut store, "t").unwrap(); + let u = i.get_resource(&mut store, "u").unwrap(); + + assert_ne!(t, u); + + let t_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t")?; + let u_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]u")?; + let t_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-t")?; + let u_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-u")?; + + let (t1,) = t_ctor.call(&mut store, (100,))?; + t_ctor.post_return(&mut store)?; + let (t2,) = t_ctor.call(&mut store, (200,))?; + t_ctor.post_return(&mut store)?; + let (u1,) = u_ctor.call(&mut store, (300,))?; + u_ctor.post_return(&mut store)?; + let (u2,) = u_ctor.call(&mut store, (400,))?; + u_ctor.post_return(&mut store)?; + + assert_eq!(t1.ty(), t); + assert_eq!(t2.ty(), t); + assert_eq!(u1.ty(), u); + assert_eq!(u2.ty(), u); + + u_dtor.call(&mut store, (u2,))?; + u_dtor.post_return(&mut store)?; + + u_dtor.call(&mut store, (u1,))?; + u_dtor.post_return(&mut store)?; + + t_dtor.call(&mut store, (t1,))?; + t_dtor.post_return(&mut store)?; + + t_dtor.call(&mut store, (t2,))?; + t_dtor.post_return(&mut store)?; + + Ok(()) +} + +#[test] +fn mismatch_intrinsics() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + (type $u' (resource (rep i32))) + + (export $t "t" (type $t')) + (export $u "u" (type $u')) + + ;; note the mismatch where this is an intrinsic for `u` but + ;; we're typing it as `t` + (core func $t_ctor (canon resource.new $u)) + + (func (export "ctor") (param "x" u32) (result (own $t)) + (canon lift (core func $t_ctor))) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; + assert_eq!( + ctor.call(&mut store, (100,)).unwrap_err().to_string(), + "unknown handle index 0" + ); + + Ok(()) +} + +#[test] +fn mismatch_resource_types() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + (type $u' (resource (rep i32))) + + (export $t "t" (type $t')) + (export $u "u" (type $u')) + + (core func $t_ctor (canon resource.new $t)) + (func (export "ctor") (param "x" u32) (result (own $t)) + (canon lift (core func $t_ctor))) + + (core func $u_dtor (canon resource.drop $u)) + (func (export "dtor") (param "x" (own $u)) + (canon lift (core func $u_dtor))) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; + let dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor")?; + + let (t,) = ctor.call(&mut store, (100,))?; + ctor.post_return(&mut store)?; + assert_eq!( + dtor.call(&mut store, (t,)).unwrap_err().to_string(), + "mismatched resource types" + ); + + Ok(()) +} + +#[test] +fn drop_in_different_places() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + + (export $t "t" (type $t')) + + (core func $ctor (canon resource.new $t)) + (func (export "ctor") (param "x" u32) (result (own $t)) + (canon lift (core func $ctor))) + + (core func $dtor (canon resource.drop $t)) + (func (export "dtor1") (param "x" (own $t)) + (canon lift (core func $dtor))) + + (component $c + (import "t" (type $t (sub resource))) + (core func $dtor (canon resource.drop $t)) + (func (export "dtor") (param "x" (own $t)) + (canon lift (core func $dtor))) + ) + (instance $i1 (instantiate $c (with "t" (type $t)))) + (instance $i2 (instantiate $c (with "t" (type $t)))) + + (export "dtor2" (func $i1 "dtor")) + (export "dtor3" (func $i2 "dtor")) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; + let dtor1 = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor1")?; + let dtor2 = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor2")?; + let dtor3 = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor3")?; + + let (t,) = ctor.call(&mut store, (100,))?; + ctor.post_return(&mut store)?; + dtor1.call(&mut store, (t,))?; + dtor1.post_return(&mut store)?; + + let (t,) = ctor.call(&mut store, (200,))?; + ctor.post_return(&mut store)?; + dtor2.call(&mut store, (t,))?; + dtor2.post_return(&mut store)?; + + let (t,) = ctor.call(&mut store, (300,))?; + ctor.post_return(&mut store)?; + dtor3.call(&mut store, (t,))?; + dtor3.post_return(&mut store)?; + + Ok(()) +} + +#[test] +fn drop_guest_twice() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + + (export $t "t" (type $t')) + + (core func $ctor (canon resource.new $t)) + (func (export "ctor") (param "x" u32) (result (own $t)) + (canon lift (core func $ctor))) + + (core func $dtor (canon resource.drop $t)) + (func (export "dtor") (param "x" (own $t)) + (canon lift (core func $dtor))) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; + let dtor = i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "dtor")?; + + let (t,) = ctor.call(&mut store, (100,))?; + ctor.post_return(&mut store)?; + dtor.call(&mut store, (&t,))?; + dtor.post_return(&mut store)?; + + assert_eq!( + dtor.call(&mut store, (&t,)).unwrap_err().to_string(), + "unknown handle index 0" + ); + + Ok(()) +} + +#[test] +fn drop_host_twice() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + + (core func $dtor (canon resource.drop $t)) + (func (export "dtor") (param "x" (own $t)) + (canon lift (core func $dtor))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + let dtor = i.get_typed_func::<(&Resource,), ()>(&mut store, "dtor")?; + + let t = Resource::new_own(100); + dtor.call(&mut store, (&t,))?; + dtor.post_return(&mut store)?; + + assert_eq!( + dtor.call(&mut store, (&t,)).unwrap_err().to_string(), + "host resource already consumed" + ); + + Ok(()) +} + +#[test] +fn manually_destroy() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t1" (type $t1 (sub resource))) + + (core module $m + (global $drops (mut i32) i32.const 0) + (global $last-drop (mut i32) i32.const 0) + + (func (export "dtor") (param i32) + (global.set $drops (i32.add (global.get $drops) (i32.const 1))) + (global.set $last-drop (local.get 0)) + ) + (func (export "drops") (result i32) global.get $drops) + (func (export "last-drop") (result i32) global.get $last-drop) + (func (export "pass") (param i32) (result i32) local.get 0) + ) + (core instance $i (instantiate $m)) + (type $t2' (resource (rep i32) (dtor (func $i "dtor")))) + (export $t2 "t2" (type $t2')) + (core func $ctor (canon resource.new $t2)) + (func (export "[constructor]t2") (param "rep" u32) (result (own $t2)) + (canon lift (core func $ctor))) + (func (export "[static]t2.drops") (result u32) + (canon lift (core func $i "drops"))) + (func (export "[static]t2.last-drop") (result u32) + (canon lift (core func $i "last-drop"))) + + (func (export "t1-pass") (param "t" (own $t1)) (result (own $t1)) + (canon lift (core func $i "pass"))) + ) + "#, + )?; + + struct MyType; + + #[derive(Default)] + struct Data { + drops: u32, + last_drop: Option, + } + + let mut store = Store::new(&engine, Data::default()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t1", |mut cx, rep| { + let data: &mut Data = cx.data_mut(); + data.drops += 1; + data.last_drop = Some(rep); + })?; + let i = linker.instantiate(&mut store, &c)?; + let t2_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t2")?; + let t2_drops = i.get_typed_func::<(), (u32,)>(&mut store, "[static]t2.drops")?; + let t2_last_drop = i.get_typed_func::<(), (u32,)>(&mut store, "[static]t2.last-drop")?; + let t1_pass = i.get_typed_func::<(Resource,), (ResourceAny,)>(&mut store, "t1-pass")?; + + // Host resources can be destroyed through `resource_drop` + let t1 = Resource::new_own(100); + let (t1,) = t1_pass.call(&mut store, (t1,))?; + t1_pass.post_return(&mut store)?; + assert_eq!(store.data().drops, 0); + assert_eq!(store.data().last_drop, None); + t1.resource_drop(&mut store)?; + assert_eq!(store.data().drops, 1); + assert_eq!(store.data().last_drop, Some(100)); + + // Guest resources can be destroyed through `resource_drop` + let (t2,) = t2_ctor.call(&mut store, (200,))?; + t2_ctor.post_return(&mut store)?; + assert_eq!(t2_drops.call(&mut store, ())?, (0,)); + t2_drops.post_return(&mut store)?; + assert_eq!(t2_last_drop.call(&mut store, ())?, (0,)); + t2_last_drop.post_return(&mut store)?; + t2.resource_drop(&mut store)?; + assert_eq!(t2_drops.call(&mut store, ())?, (1,)); + t2_drops.post_return(&mut store)?; + assert_eq!(t2_last_drop.call(&mut store, ())?, (200,)); + t2_last_drop.post_return(&mut store)?; + + // Wires weren't crossed to drop more resources + assert_eq!(store.data().drops, 1); + assert_eq!(store.data().last_drop, Some(100)); + + Ok(()) +} + +#[test] +fn dynamic_type() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t1" (type $t1 (sub resource))) + (type $t2' (resource (rep i32))) + (export $t2 "t2" (type $t2')) + (core func $f (canon resource.drop $t2)) + + (func (export "a") (param "x" (own $t1)) + (canon lift (core func $f))) + (func (export "b") (param "x" (tuple (own $t2))) + (canon lift (core func $f))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t1", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + + let a = i.get_func(&mut store, "a").unwrap(); + let b = i.get_func(&mut store, "b").unwrap(); + let t2 = i.get_resource(&mut store, "t2").unwrap(); + + let a_params = a.params(&store); + assert_eq!(a_params[0], Type::Own(ResourceType::host::())); + let b_params = b.params(&store); + match &b_params[0] { + Type::Tuple(t) => { + assert_eq!(t.types().len(), 1); + let t0 = t.types().next().unwrap(); + assert_eq!(t0, Type::Own(t2)); + } + _ => unreachable!(), + } + + Ok(()) +} + +#[test] +fn dynamic_val() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t1" (type $t1 (sub resource))) + (type $t2' (resource (rep i32))) + (export $t2 "t2" (type $t2')) + (core func $f (canon resource.new $t2)) + + (core module $m + (func (export "pass") (param i32) (result i32) + (local.get 0))) + (core instance $i (instantiate $m)) + + (func (export "a") (param "x" (own $t1)) (result (own $t1)) + (canon lift (core func $i "pass"))) + (func (export "b") (param "x" u32) (result (own $t2)) + (canon lift (core func $f))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t1", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + + let a = i.get_func(&mut store, "a").unwrap(); + let a_typed = i.get_typed_func::<(Resource,), (ResourceAny,)>(&mut store, "a")?; + let b = i.get_func(&mut store, "b").unwrap(); + let t2 = i.get_resource(&mut store, "t2").unwrap(); + + let t1 = Resource::new_own(100); + let (t1,) = a_typed.call(&mut store, (t1,))?; + a_typed.post_return(&mut store)?; + assert_eq!(t1.ty(), ResourceType::host::()); + + let mut results = [Val::Bool(false)]; + a.call(&mut store, &[Val::Resource(t1)], &mut results)?; + a.post_return(&mut store)?; + match &results[0] { + Val::Resource(resource) => { + assert_eq!(resource.ty(), ResourceType::host::()); + } + _ => unreachable!(), + } + + b.call(&mut store, &[Val::U32(200)], &mut results)?; + match &results[0] { + Val::Resource(resource) => { + assert_eq!(resource.ty(), t2); + } + _ => unreachable!(), + } + + Ok(()) +} + +#[test] +fn cannot_reenter_during_import() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "f" (func $f)) + + (core func $f (canon lower (func $f))) + + (core module $m + (import "" "f" (func $f)) + (func (export "call") call $f) + (func (export "dtor") (param i32) unreachable) + ) + + (core instance $i (instantiate $m + (with "" (instance + (export "f" (func $f)) + )) + )) + + (type $t2' (resource (rep i32) (dtor (func $i "dtor")))) + (export $t2 "t" (type $t2')) + (core func $ctor (canon resource.new $t2)) + (func (export "ctor") (param "x" u32) (result (own $t2)) + (canon lift (core func $ctor))) + + (func (export "call") (canon lift (core func $i "call"))) + ) + "#, + )?; + + let mut store = Store::new(&engine, None); + let mut linker = Linker::new(&engine); + linker.root().func_wrap("f", |mut cx, ()| { + let data: &mut Option = cx.data_mut(); + let err = data.take().unwrap().resource_drop(cx).unwrap_err(); + assert_eq!( + err.downcast_ref(), + Some(&Trap::CannotEnterComponent), + "bad error: {err:?}" + ); + Ok(()) + })?; + let i = linker.instantiate(&mut store, &c)?; + + let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; + let call = i.get_typed_func::<(), ()>(&mut store, "call")?; + + let (resource,) = ctor.call(&mut store, (100,))?; + ctor.post_return(&mut store)?; + *store.data_mut() = Some(resource); + call.call(&mut store, ())?; + + Ok(()) +} + +#[test] +fn active_borrows_at_end_of_call() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + + (core module $m + (func (export "f") (param i32)) + ) + (core instance $i (instantiate $m)) + + (func (export "f") (param "x" (borrow $t)) + (canon lift (core func $i "f"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f")?; + + let resource = Resource::new_own(1); + f.call(&mut store, (&resource,))?; + let err = f.post_return(&mut store).unwrap_err(); + assert_eq!( + err.to_string(), + "borrow handles still remain at the end of the call", + ); + + Ok(()) +} + +#[test] +fn thread_through_borrow() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + (import "f" (func $f (param "x" (borrow $t)))) + + (core func $f (canon lower (func $f))) + (core func $drop (canon resource.drop $t)) + + (core module $m + (import "" "f" (func $f (param i32))) + (import "" "drop" (func $drop (param i32))) + (func (export "f2") (param i32) + (call $f (local.get 0)) + (call $f (local.get 0)) + (call $drop (local.get 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "f" (func $f)) + (export "drop" (func $drop)) + )) + )) + + (func (export "f2") (param "x" (borrow $t)) + (canon lift (core func $i "f2"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + linker + .root() + .func_wrap("f", |_cx, (r,): (Resource,)| { + assert!(!r.owned()); + assert_eq!(r.rep(), 100); + Ok(()) + })?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; + + let resource = Resource::new_own(100); + f.call(&mut store, (&resource,))?; + f.post_return(&mut store)?; + Ok(()) +} + +#[test] +fn cannot_use_borrow_for_own() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + + (core module $m + (func (export "f") (param i32) (result i32) + local.get 0 + ) + ) + (core instance $i (instantiate $m)) + + (func (export "f") (param "x" (borrow $t)) (result (own $t)) + (canon lift (core func $i "f"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), (Resource,)>(&mut store, "f")?; + + let resource = Resource::new_own(100); + let err = f.call(&mut store, (&resource,)).unwrap_err(); + assert_eq!(err.to_string(), "cannot lift own resource from a borrow"); + Ok(()) +} + +#[test] +fn passthrough_wrong_type() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + (import "f" (func $f (param "a" (borrow $t)) (result (own $t)))) + + (core func $f (canon lower (func $f))) + + (core module $m + (import "" "f" (func $f (param i32) (result i32))) + (func (export "f2") (param i32) + (drop (call $f (local.get 0))) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "f" (func $f)) + )) + )) + + (func (export "f2") (param "x" (borrow $t)) + (canon lift (core func $i "f2"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + linker + .root() + .func_wrap("f", |_cx, (r,): (Resource,)| Ok((r,)))?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; + + let resource = Resource::new_own(100); + let err = f.call(&mut store, (&resource,)).unwrap_err(); + assert!( + format!("{err:?}").contains("cannot lower a `borrow` resource into an `own`"), + "bad error: {err:?}" + ); + Ok(()) +} + +#[test] +fn pass_moved_resource() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + (core module $m + (func (export "f") (param i32 i32)) + ) + (core instance $i (instantiate $m)) + + (func (export "f") (param "x" (own $t)) (param "y" (borrow $t)) + (canon lift (core func $i "f"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource, &Resource), ()>(&mut store, "f")?; + + let resource = Resource::new_own(100); + let err = f.call(&mut store, (&resource, &resource)).unwrap_err(); + assert!( + format!("{err:?}").contains("host resource already consumed"), + "bad error: {err:?}" + ); + Ok(()) +} + +#[test] +fn type_mismatch() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + (export $t "t" (type $t')) + + (core func $drop (canon resource.drop $t)) + + (func (export "f1") (param "x" (own $t)) + (canon lift (core func $drop))) + (func (export "f2") (param "x" (borrow $t)) + (canon lift (core func $drop))) + (func (export "f3") (param "x" u32) + (canon lift (core func $drop))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + + assert!(i + .get_typed_func::<(&Resource,), ()>(&mut store, "f1") + .is_err()); + assert!(i + .get_typed_func::<(&ResourceAny,), ()>(&mut store, "f1") + .is_ok()); + + assert!(i + .get_typed_func::<(&Resource,), ()>(&mut store, "f2") + .is_err()); + assert!(i + .get_typed_func::<(&ResourceAny,), ()>(&mut store, "f2") + .is_ok()); + + assert!(i + .get_typed_func::<(&Resource,), ()>(&mut store, "f3") + .is_err()); + assert!(i + .get_typed_func::<(&ResourceAny,), ()>(&mut store, "f3") + .is_err()); + assert!(i.get_typed_func::<(u32,), ()>(&mut store, "f3").is_ok()); + + Ok(()) +} + +#[test] +fn drop_no_dtor() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + (export $t "t" (type $t')) + + (core func $ctor (canon resource.new $t)) + + (func (export "ctor") (param "x" u32) (result (own $t)) + (canon lift (core func $ctor))) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; + let (resource,) = ctor.call(&mut store, (100,))?; + ctor.post_return(&mut store)?; + resource.resource_drop(&mut store)?; + + Ok(()) +} + +#[test] +fn host_borrow_as_resource_any() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + (import "f" (func $f (param "f" (borrow $t)))) + + (core func $f (canon lower (func $f))) + + (core module $m + (import "" "f" (func $f (param i32))) + (func (export "f2") (param i32) + (call $f (local.get 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "f" (func $f)) + )) + )) + + (func (export "f2") (param "x" (borrow $t)) + (canon lift (core func $i "f2"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + + // First test the above component where the host properly drops the argument + { + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + linker + .root() + .func_wrap("f", |mut cx, (r,): (ResourceAny,)| { + r.resource_drop(&mut cx)?; + Ok(()) + })?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; + + let resource = Resource::new_own(100); + f.call(&mut store, (&resource,))?; + } + + // Then also test the case where the host forgets a drop + { + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + linker.root().func_wrap("f", |_cx, (_r,): (ResourceAny,)| { + // ... no drop here + Ok(()) + })?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; + + let resource = Resource::new_own(100); + let err = f.call(&mut store, (&resource,)).unwrap_err(); + assert!( + format!("{err:?}").contains("borrow handles still remain at the end of the call"), + "bad error: {err:?}" + ); + } + Ok(()) +} + +#[test] +fn pass_guest_back_as_borrow() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (type $t' (resource (rep i32))) + + (export $t "t" (type $t')) + + (core func $new (canon resource.new $t)) + + (core module $m + (import "" "new" (func $new (param i32) (result i32))) + + (func (export "mk") (result i32) + (call $new (i32.const 100)) + ) + + (func (export "take") (param i32) + (if (i32.ne (local.get 0) (i32.const 100)) (unreachable)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "new" (func $new)) + )) + )) + + (func (export "mk") (result (own $t)) + (canon lift (core func $i "mk"))) + (func (export "take") (param "x" (borrow $t)) + (canon lift (core func $i "take"))) + ) + "#, + )?; + + let mut store = Store::new(&engine, ()); + let i = Linker::new(&engine).instantiate(&mut store, &c)?; + let mk = i.get_typed_func::<(), (ResourceAny,)>(&mut store, "mk")?; + let take = i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "take")?; + + let (resource,) = mk.call(&mut store, ())?; + mk.post_return(&mut store)?; + take.call(&mut store, (&resource,))?; + take.post_return(&mut store)?; + + resource.resource_drop(&mut store)?; + + // Should not be valid to use `resource` again + let err = take.call(&mut store, (&resource,)).unwrap_err(); + assert_eq!(err.to_string(), "unknown handle index 0"); + + Ok(()) +} + +#[test] +fn pass_host_borrow_to_guest() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + + (core func $drop (canon resource.drop $t)) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (func (export "take") (param i32) + (call $drop (local.get 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + )) + )) + + (func (export "take") (param "x" (borrow $t)) + (canon lift (core func $i "take"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + let i = linker.instantiate(&mut store, &c)?; + let take = i.get_typed_func::<(&Resource,), ()>(&mut store, "take")?; + + let resource = Resource::new_borrow(100); + take.call(&mut store, (&resource,))?; + take.post_return(&mut store)?; + + Ok(()) +} + +#[test] +fn drop_on_owned_resource() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t" (type $t (sub resource))) + (import "[constructor]t" (func $ctor (result (own $t)))) + (import "[method]t.foo" (func $foo (param "self" (borrow $t)) (result (list u8)))) + + (core func $ctor (canon lower (func $ctor))) + (core func $drop (canon resource.drop $t)) + + (core module $m1 + (import "" "drop" (func $drop (param i32))) + (memory (export "memory") 1) + (global $to-drop (export "to-drop") (mut i32) (i32.const 0)) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) + (call $drop (global.get $to-drop)) + unreachable) + ) + (core instance $i1 (instantiate $m1 + (with "" (instance + (export "drop" (func $drop)) + )) + )) + + (core func $foo (canon lower (func $foo) + (memory $i1 "memory") + (realloc (func $i1 "realloc")))) + + (core module $m2 + (import "" "ctor" (func $ctor (result i32))) + (import "" "foo" (func $foo (param i32 i32))) + (import "i1" "to-drop" (global $to-drop (mut i32))) + + (func (export "f") + (local $r i32) + (local.set $r (call $ctor)) + (global.set $to-drop (local.get $r)) + (call $foo + (local.get $r) + (i32.const 200)) + ) + ) + (core instance $i2 (instantiate $m2 + (with "" (instance + (export "ctor" (func $ctor)) + (export "foo" (func $foo)) + )) + (with "i1" (instance $i1)) + )) + (func (export "f") (canon lift (core func $i2 "f"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t", |_, _| {})?; + linker.root().func_wrap("[constructor]t", |_cx, ()| { + Ok((Resource::::new_own(300),)) + })?; + linker + .root() + .func_wrap("[method]t.foo", |_cx, (r,): (Resource,)| { + assert!(!r.owned()); + Ok((vec![2u8],)) + })?; + let i = linker.instantiate(&mut store, &c)?; + let f = i.get_typed_func::<(), ()>(&mut store, "f")?; + + let err = f.call(&mut store, ()).unwrap_err(); + assert!( + format!("{err:?}").contains("cannot remove owned resource while borrowed"), + "bad error: {err:?}" + ); + + Ok(()) +} + +#[test] +fn guest_different_host_same() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "t1" (type $t1 (sub resource))) + (import "t2" (type $t2 (sub resource))) + + (import "f" (func $f (param "a" (borrow $t1)) (param "b" (borrow $t2)))) + + (export $g1 "g1" (type $t1)) + (export $g2 "g2" (type $t2)) + + (core func $f (canon lower (func $f))) + (core func $drop1 (canon resource.drop $t1)) + (core func $drop2 (canon resource.drop $t2)) + + (core module $m + (import "" "f" (func $f (param i32 i32))) + (import "" "drop1" (func $drop1 (param i32))) + (import "" "drop2" (func $drop2 (param i32))) + + (func (export "f") (param i32 i32) + ;; separate tables both have initial index of 0 + (if (i32.ne (local.get 0) (i32.const 0)) (unreachable)) + (if (i32.ne (local.get 1) (i32.const 0)) (unreachable)) + + ;; host should end up getting the same resource + (call $f (local.get 0) (local.get 1)) + + ;; drop our borrows + (call $drop1 (local.get 0)) + (call $drop2 (local.get 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "f" (func $f)) + (export "drop1" (func $drop1)) + (export "drop2" (func $drop2)) + )) + )) + + (func (export "f2") (param "a" (borrow $g1)) (param "b" (borrow $g2)) + (canon lift (core func $i "f"))) + ) + "#, + )?; + + struct MyType; + + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.root().resource::("t1", |_, _| {})?; + linker.root().resource::("t2", |_, _| {})?; + linker.root().func_wrap( + "f", + |_cx, (r1, r2): (Resource, Resource)| { + assert!(!r1.owned()); + assert!(!r2.owned()); + assert_eq!(r1.rep(), 100); + assert_eq!(r2.rep(), 100); + Ok(()) + }, + )?; + let i = linker.instantiate(&mut store, &c)?; + let f = i.get_typed_func::<(&Resource, &Resource), ()>(&mut store, "f2")?; + + let t1 = i.get_resource(&mut store, "g1").unwrap(); + let t2 = i.get_resource(&mut store, "g2").unwrap(); + assert_eq!(t1, t2); + assert_eq!(t1, ResourceType::host::()); + + let resource = Resource::new_own(100); + f.call(&mut store, (&resource, &resource))?; + f.post_return(&mut store)?; + + Ok(()) +} diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast new file mode 100644 index 000000000000..3d521beffa2a --- /dev/null +++ b/tests/misc_testsuite/component-model/resources.wast @@ -0,0 +1,958 @@ +;; bare bones "intrinsics work" +(component + (type $r (resource (rep i32))) + (core func $rep (canon resource.rep $r)) + (core func $new (canon resource.new $r)) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "rep" (func $rep (param i32) (result i32))) + (import "" "new" (func $new (param i32) (result i32))) + (import "" "drop" (func $drop (param i32))) + + (func $start + (local $r i32) + (local.set $r (call $new (i32.const 100))) + + (if (i32.ne (local.get $r) (i32.const 0)) (unreachable)) + (if (i32.ne (call $rep (local.get $r)) (i32.const 100)) (unreachable)) + + (call $drop (local.get $r)) + ) + + (start $start) + ) + (core instance (instantiate $m + (with "" (instance + (export "rep" (func $rep)) + (export "new" (func $new)) + (export "drop" (func $drop)) + )) + )) +) + +;; cannot call `resource.drop` on a nonexistent resource +(component + (type $r (resource (rep i32))) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "drop" (func $drop (param i32))) + + (func (export "r") + (call $drop (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + )) + )) + + (func (export "r") (canon lift (core func $i "r"))) +) +(assert_trap (invoke "r") "unknown handle index 0") + +;; cannot call `resource.rep` on a nonexistent resource +(component + (type $r (resource (rep i32))) + (core func $rep (canon resource.rep $r)) + + (core module $m + (import "" "rep" (func $rep (param i32) (result i32))) + + (func (export "r") + (drop (call $rep (i32.const 0))) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "rep" (func $rep)) + )) + )) + + (func (export "r") (canon lift (core func $i "r"))) +) +(assert_trap (invoke "r") "unknown handle index 0") + +;; index reuse behavior of handles +(component + (type $r (resource (rep i32))) + (core func $rep (canon resource.rep $r)) + (core func $new (canon resource.new $r)) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "rep" (func $rep (param i32) (result i32))) + (import "" "new" (func $new (param i32) (result i32))) + (import "" "drop" (func $drop (param i32))) + + (func $start + (local $r1 i32) + (local $r2 i32) + (local $r3 i32) + (local $r4 i32) + + ;; resources assigned sequentially + (local.set $r1 (call $new (i32.const 100))) + (if (i32.ne (local.get $r1) (i32.const 0)) (unreachable)) + + (local.set $r2 (call $new (i32.const 200))) + (if (i32.ne (local.get $r2) (i32.const 1)) (unreachable)) + + (local.set $r3 (call $new (i32.const 300))) + (if (i32.ne (local.get $r3) (i32.const 2)) (unreachable)) + + ;; representations all look good + (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (unreachable)) + (if (i32.ne (call $rep (local.get $r2)) (i32.const 200)) (unreachable)) + (if (i32.ne (call $rep (local.get $r3)) (i32.const 300)) (unreachable)) + + ;; reallocate r2 + (call $drop (local.get $r2)) + (local.set $r2 (call $new (i32.const 400))) + + ;; should have reused index 1 + (if (i32.ne (local.get $r2) (i32.const 1)) (unreachable)) + + ;; representations all look good + (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (unreachable)) + (if (i32.ne (call $rep (local.get $r2)) (i32.const 400)) (unreachable)) + (if (i32.ne (call $rep (local.get $r3)) (i32.const 300)) (unreachable)) + + ;; deallocate, then reallocate + (call $drop (local.get $r1)) + (call $drop (local.get $r2)) + (call $drop (local.get $r3)) + + (local.set $r1 (call $new (i32.const 500))) + (local.set $r2 (call $new (i32.const 600))) + (local.set $r3 (call $new (i32.const 700))) + + ;; representations all look good + (if (i32.ne (call $rep (local.get $r1)) (i32.const 500)) (unreachable)) + (if (i32.ne (call $rep (local.get $r2)) (i32.const 600)) (unreachable)) + (if (i32.ne (call $rep (local.get $r3)) (i32.const 700)) (unreachable)) + + ;; indices should be lifo + (if (i32.ne (local.get $r1) (i32.const 2)) (unreachable)) + (if (i32.ne (local.get $r2) (i32.const 1)) (unreachable)) + (if (i32.ne (local.get $r3) (i32.const 0)) (unreachable)) + + ;; bump one more time + (local.set $r4 (call $new (i32.const 800))) + (if (i32.ne (local.get $r4) (i32.const 3)) (unreachable)) + + ;; deallocate everything + (call $drop (local.get $r1)) + (call $drop (local.get $r2)) + (call $drop (local.get $r3)) + (call $drop (local.get $r4)) + ) + + (start $start) + ) + (core instance (instantiate $m + (with "" (instance + (export "rep" (func $rep)) + (export "new" (func $new)) + (export "drop" (func $drop)) + )) + )) +) + +(assert_unlinkable + (component + (import "host" (instance + (export "missing" (type (sub resource))) + )) + ) + "expected resource found nothing") +(assert_unlinkable + (component + (import "host" (instance + (export "return-three" (type (sub resource))) + )) + ) + "expected resource found func") + +;; all resources can be uniquely imported +(component + (import "host" (instance + (export "resource1" (type (sub resource))) + (export "resource2" (type (sub resource))) + (export "resource1-again" (type (sub resource))) + )) +) + +;; equality constraints also work +(component + (import "host" (instance + (export $r1 "resource1" (type (sub resource))) + (export "resource2" (type (sub resource))) + (export "resource1-again" (type (eq $r1))) + )) +) + +;; equality constraints are checked if resources are supplied +(assert_unlinkable + (component + (import "host" (instance + (export "resource1" (type (sub resource))) + (export $r1 "resource2" (type (sub resource))) + (export "resource1-again" (type (eq $r1))) + )) + ) + "mismatched resource types") + +;; equality constraints mean that types don't need to be supplied +(component + (import "host" (instance + (export $r1 "resource1" (type (sub resource))) + (export "resource2" (type (sub resource))) + (export "this-name-is-not-provided-in-the-wast-harness" (type (eq $r1))) + )) +) + +;; simple properties of handles +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + (export "[static]resource1.assert" (func (param "r" (own $r)) (param "rep" u32))) + )) + (alias export $host "resource1" (type $r)) + (alias export $host "[constructor]resource1" (func $ctor)) + (alias export $host "[static]resource1.assert" (func $assert)) + + (core func $drop (canon resource.drop $r)) + (core func $ctor (canon lower (func $ctor))) + (core func $assert (canon lower (func $assert))) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (import "" "ctor" (func $ctor (param i32) (result i32))) + (import "" "assert" (func $assert (param i32 i32))) + + (func $start + (local $r1 i32) + (local $r2 i32) + (local.set $r1 (call $ctor (i32.const 100))) + (local.set $r2 (call $ctor (i32.const 200))) + + ;; assert r1/r2 are sequential + (if (i32.ne (local.get $r1) (i32.const 0)) (unreachable)) + (if (i32.ne (local.get $r2) (i32.const 1)) (unreachable)) + + ;; reallocate r1 and it should be reassigned the same index + (call $drop (local.get $r1)) + (local.set $r1 (call $ctor (i32.const 300))) + (if (i32.ne (local.get $r1) (i32.const 0)) (unreachable)) + + ;; internal values should match + (call $assert (local.get $r1) (i32.const 300)) + (call $assert (local.get $r2) (i32.const 200)) + ) + + (start $start) + ) + (core instance (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + (export "ctor" (func $ctor)) + (export "assert" (func $assert)) + )) + )) +) + +;; Using an index that has never been valid is a trap +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[static]resource1.assert" (func (param "r" (own $r)) (param "rep" u32))) + )) + (alias export $host "resource1" (type $r)) + (alias export $host "[static]resource1.assert" (func $assert)) + (core func $assert (canon lower (func $assert))) + + (core module $m + (import "" "assert" (func $assert (param i32 i32))) + + (func (export "f") + (call $assert (i32.const 0) (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "assert" (func $assert)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) +) + +(assert_trap (invoke "f") "unknown handle index") + +;; Using an index which was previously valid but no longer valid is also a trap. +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + (export "[static]resource1.assert" (func (param "r" (own $r)) (param "rep" u32))) + )) + (alias export $host "[constructor]resource1" (func $ctor)) + (alias export $host "[static]resource1.assert" (func $assert)) + + (core func $assert (canon lower (func $assert))) + (core func $ctor (canon lower (func $ctor))) + + (core module $m + (import "" "assert" (func $assert (param i32 i32))) + (import "" "ctor" (func $ctor (param i32) (result i32))) + + (global $handle (mut i32) i32.const 0) + + (func (export "f") + (global.set $handle (call $ctor (i32.const 100))) + (call $assert (global.get $handle) (i32.const 100)) + ) + + (func (export "f2") + (call $assert (global.get $handle) (i32.const 100)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "assert" (func $assert)) + (export "ctor" (func $ctor)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) + (func (export "f2") (canon lift (core func $i "f2"))) +) + +(assert_return (invoke "f")) +(assert_trap (invoke "f2") "unknown handle index") + +;; Also invalid to pass a previously valid handle to the drop intrinsic +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + )) + (alias export $host "resource1" (type $r)) + (alias export $host "[constructor]resource1" (func $ctor)) + + (core func $drop (canon resource.drop $r)) + (core func $ctor (canon lower (func $ctor))) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (import "" "ctor" (func $ctor (param i32) (result i32))) + + (global $handle (mut i32) i32.const 0) + + (func (export "f") + (global.set $handle (call $ctor (i32.const 100))) + (call $drop (global.get $handle)) + ) + + (func (export "f2") + (call $drop (global.get $handle)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "ctor" (func $ctor)) + (export "drop" (func $drop)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) + (func (export "f2") (canon lift (core func $i "f2"))) +) + +(assert_return (invoke "f")) +(assert_trap (invoke "f2") "unknown handle index") + +;; If an inner component instantiates a resource then an outer component +;; should not implicitly have access to that resource. +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + )) + + ;; an inner component which upon instantiation will invoke the constructor, + ;; assert that it's zero, and then forget about it. + (component $inner + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + )) + (alias export $host "[constructor]resource1" (func $ctor)) + + (core func $ctor (canon lower (func $ctor))) + + (core module $m + (import "" "ctor" (func $ctor (param i32) (result i32))) + + (func $start + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (unreachable)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance (export "ctor" (func $ctor)))) + )) + ) + (instance $i (instantiate $inner (with "host" (instance $host)))) + + ;; the rest of this component which is a single function that invokes `drop` + ;; for index 0. The index 0 should be valid within the above component, but + ;; it is not valid within this component + (alias export $host "resource1" (type $r)) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "drop" (func $drop (param i32))) + + (func (export "f") + (call $drop (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) +) + +(assert_trap (invoke "f") "unknown handle index") + +;; Same as the above test, but for resources defined within a component +(component + (component $inner + (type $r (resource (rep i32))) + + (core func $ctor (canon resource.new $r)) + + (core module $m + (import "" "ctor" (func $ctor (param i32) (result i32))) + + (func $start + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (unreachable)) + ) + (start $start) + ) + (core instance $i (instantiate $m + (with "" (instance (export "ctor" (func $ctor)))) + )) + (export "r" (type $r)) + ) + (instance $i (instantiate $inner)) + + ;; the rest of this component which is a single function that invokes `drop` + ;; for index 0. The index 0 should be valid within the above component, but + ;; it is not valid within this component + (alias export $i "r" (type $r)) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "drop" (func $drop (param i32))) + + (func (export "f") + (call $drop (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) +) + +(assert_trap (invoke "f") "unknown handle index") + +;; Each instantiation of a component generates a unique resource type, so +;; allocating in one component and deallocating in another should fail. +(component + (component $inner + (type $r (resource (rep i32))) + + (core func $ctor (canon resource.new $r)) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "ctor" (func $ctor (param i32) (result i32))) + (import "" "drop" (func $drop (param i32))) + + (func (export "alloc") + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (unreachable)) + ) + (func (export "dealloc") + (call $drop (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "ctor" (func $ctor)) + (export "drop" (func $drop)) + )) + )) + (func (export "alloc") (canon lift (core func $i "alloc"))) + (func (export "dealloc") (canon lift (core func $i "dealloc"))) + ) + (instance $i1 (instantiate $inner)) + (instance $i2 (instantiate $inner)) + + (alias export $i1 "alloc" (func $alloc_in_1)) + (alias export $i1 "dealloc" (func $dealloc_in_1)) + (alias export $i2 "alloc" (func $alloc_in_2)) + (alias export $i2 "dealloc" (func $dealloc_in_2)) + + (export "alloc-in1" (func $alloc_in_1)) + (export "dealloc-in1" (func $dealloc_in_1)) + (export "alloc-in2" (func $alloc_in_2)) + (export "dealloc-in2" (func $dealloc_in_2)) +) + +(assert_return (invoke "alloc-in1")) +(assert_return (invoke "dealloc-in1")) +(assert_return (invoke "alloc-in1")) +(assert_return (invoke "alloc-in2")) +(assert_return (invoke "dealloc-in2")) +(assert_trap (invoke "dealloc-in2") "unknown handle index") + +;; Same as above, but the same host resource type is imported into a +;; component that is instantiated twice. Each component instance should +;; receive different tables tracking resources so a resource allocated in one +;; should not be visible in the other. +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + )) + (alias export $host "resource1" (type $r)) + (alias export $host "[constructor]resource1" (func $ctor)) + + (component $inner + (import "r" (type $r (sub resource))) + (import "[constructor]r" (func $ctor (param "r" u32) (result (own $r)))) + + (core func $ctor (canon lower (func $ctor))) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "ctor" (func $ctor (param i32) (result i32))) + (import "" "drop" (func $drop (param i32))) + + (func (export "alloc") + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (unreachable)) + ) + (func (export "dealloc") + (call $drop (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "ctor" (func $ctor)) + (export "drop" (func $drop)) + )) + )) + (func (export "alloc") (canon lift (core func $i "alloc"))) + (func (export "dealloc") (canon lift (core func $i "dealloc"))) + ) + (instance $i1 (instantiate $inner + (with "r" (type $r)) + (with "[constructor]r" (func $ctor)) + )) + (instance $i2 (instantiate $inner + (with "r" (type $r)) + (with "[constructor]r" (func $ctor)) + )) + + (alias export $i1 "alloc" (func $alloc_in_1)) + (alias export $i1 "dealloc" (func $dealloc_in_1)) + (alias export $i2 "alloc" (func $alloc_in_2)) + (alias export $i2 "dealloc" (func $dealloc_in_2)) + + (export "alloc-in1" (func $alloc_in_1)) + (export "dealloc-in1" (func $dealloc_in_1)) + (export "alloc-in2" (func $alloc_in_2)) + (export "dealloc-in2" (func $dealloc_in_2)) +) + +(assert_return (invoke "alloc-in1")) +(assert_return (invoke "dealloc-in1")) +(assert_return (invoke "alloc-in1")) +(assert_return (invoke "alloc-in2")) +(assert_return (invoke "dealloc-in2")) +(assert_trap (invoke "dealloc-in2") "unknown handle index") + +;; Multiple copies of intrinsics all work +(component + (type $r (resource (rep i32))) + + (core func $new1 (canon resource.new $r)) + (core func $new2 (canon resource.new $r)) + (core func $drop1 (canon resource.drop $r)) + (core func $drop2 (canon resource.drop $r)) + + (core module $m + (import "" "new1" (func $new1 (param i32) (result i32))) + (import "" "new2" (func $new2 (param i32) (result i32))) + (import "" "drop1" (func $drop1 (param i32))) + (import "" "drop2" (func $drop2 (param i32))) + + (func $start + ;; 2x2 matrix of pairing new/drop + (call $drop1 (call $new1 (i32.const 101))) + (call $drop2 (call $new1 (i32.const 102))) + (call $drop1 (call $new2 (i32.const 103))) + (call $drop2 (call $new2 (i32.const 104))) + + ;; should be referencing the same namespace + (if (i32.ne (call $new1 (i32.const 105)) (i32.const 0)) (unreachable)) + (if (i32.ne (call $new2 (i32.const 105)) (i32.const 1)) (unreachable)) + + ;; use different drops out of order + (call $drop2 (i32.const 0)) + (call $drop1 (i32.const 1)) + ) + + (start $start) + ) + + (core instance (instantiate $m + (with "" (instance + (export "new1" (func $new1)) + (export "new2" (func $new2)) + (export "drop1" (func $drop1)) + (export "drop2" (func $drop2)) + )) + )) +) + +;; u32::MAX isn't special in some weird way, it's just probably always invalid +;; because that's a lot of handles. +(component + (type $r (resource (rep i32))) + + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "drop" (func $drop (param i32))) + + (func (export "f") + (call $drop (i32.const 0xffffffff)) + ) + ) + + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + )) + )) + (func (export "f") (canon lift (core func $i "f"))) +) +(assert_trap (invoke "f") "unknown handle index") + +;; Test behavior of running a destructor for local resources +(component + (core module $m1 + (global $drops (mut i32) i32.const 0) + (global $last_drop (mut i32) i32.const -1) + + (func (export "dtor") (param i32) + (global.set $drops (i32.add (global.get $drops) (i32.const 1))) + (global.set $last_drop (local.get 0)) + ) + (func (export "drops") (result i32) global.get $drops) + (func (export "last-drop") (result i32) global.get $last_drop) + ) + (core instance $i1 (instantiate $m1)) + + (type $r1 (resource (rep i32))) + (type $r2 (resource (rep i32) (dtor (func $i1 "dtor")))) + + (core func $drop1 (canon resource.drop $r1)) + (core func $drop2 (canon resource.drop $r2)) + (core func $new1 (canon resource.new $r1)) + (core func $new2 (canon resource.new $r2)) + + (core module $m2 + (import "" "drop1" (func $drop1 (param i32))) + (import "" "drop2" (func $drop2 (param i32))) + (import "" "new1" (func $new1 (param i32) (result i32))) + (import "" "new2" (func $new2 (param i32) (result i32))) + (import "i1" "drops" (func $drops (result i32))) + (import "i1" "last-drop" (func $last-drop (result i32))) + + (func $start + (local $r1 i32) + (local $r2 i32) + + (local.set $r1 (call $new1 (i32.const 100))) + (local.set $r2 (call $new2 (i32.const 200))) + + ;; both should be index 0 + (if (i32.ne (local.get $r1) (i32.const 0)) (unreachable)) + (if (i32.ne (local.get $r2) (i32.const 0)) (unreachable)) + + ;; nothing should be dropped yet + (if (i32.ne (call $drops) (i32.const 0)) (unreachable)) + (if (i32.ne (call $last-drop) (i32.const -1)) (unreachable)) + + ;; dropping a resource without a destructor is ok, but shouldn't tamper + ;; with anything. + (call $drop1 (local.get $r1)) + (if (i32.ne (call $drops) (i32.const 0)) (unreachable)) + (if (i32.ne (call $last-drop) (i32.const -1)) (unreachable)) + + ;; drop r2 which should record a drop and additionally record the private + ;; representation value which was dropped + (call $drop2 (local.get $r2)) + (if (i32.ne (call $drops) (i32.const 1)) (unreachable)) + (if (i32.ne (call $last-drop) (i32.const 200)) (unreachable)) + + ;; do it all over again + (local.set $r2 (call $new2 (i32.const 300))) + (call $drop2 (local.get $r2)) + (if (i32.ne (call $drops) (i32.const 2)) (unreachable)) + (if (i32.ne (call $last-drop) (i32.const 300)) (unreachable)) + ) + + (start $start) + ) + + (core instance $i2 (instantiate $m2 + (with "" (instance + (export "drop1" (func $drop1)) + (export "drop2" (func $drop2)) + (export "new1" (func $new1)) + (export "new2" (func $new2)) + )) + (with "i1" (instance $i1)) + )) +) + +;; Test dropping a host resource +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + (export "[static]resource1.last-drop" (func (result u32))) + (export "[static]resource1.drops" (func (result u32))) + )) + + (alias export $host "resource1" (type $r)) + (alias export $host "[constructor]resource1" (func $ctor)) + (alias export $host "[static]resource1.last-drop" (func $last-drop)) + (alias export $host "[static]resource1.drops" (func $drops)) + + (core func $drop (canon resource.drop $r)) + (core func $ctor (canon lower (func $ctor))) + (core func $last-drop (canon lower (func $last-drop))) + (core func $drops (canon lower (func $drops))) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (import "" "ctor" (func $ctor (param i32) (result i32))) + (import "" "last-drop" (func $last-drop (result i32))) + (import "" "drops" (func $raw-drops (result i32))) + + (global $init-drop-cnt (mut i32) i32.const 0) + + (func $drops (result i32) + (i32.sub (call $raw-drops) (global.get $init-drop-cnt)) + ) + + (func $start + (local $r1 i32) + (global.set $init-drop-cnt (call $raw-drops)) + + (local.set $r1 (call $ctor (i32.const 100))) + + ;; should be no drops yet + (if (i32.ne (call $drops) (i32.const 0)) (unreachable)) + + ;; should count a drop + (call $drop (local.get $r1)) + (if (i32.ne (call $drops) (i32.const 1)) (unreachable)) + (if (i32.ne (call $last-drop) (i32.const 100)) (unreachable)) + + ;; do it again to be sure + (local.set $r1 (call $ctor (i32.const 200))) + (call $drop (local.get $r1)) + (if (i32.ne (call $drops) (i32.const 2)) (unreachable)) + (if (i32.ne (call $last-drop) (i32.const 200)) (unreachable)) + ) + + (start $start) + ) + (core instance (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + (export "ctor" (func $ctor)) + (export "last-drop" (func $last-drop)) + (export "drops" (func $drops)) + )) + )) +) + +;; Test some bare-bones basics of borrowed resources +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + (export "[method]resource1.simple" (func (param "self" (borrow $r)) (param "rep" u32))) + (export "[method]resource1.take-borrow" (func (param "self" (borrow $r)) (param "b" (borrow $r)))) + (export "[method]resource1.take-own" (func (param "self" (borrow $r)) (param "b" (own $r)))) + )) + + (alias export $host "resource1" (type $r)) + (alias export $host "[constructor]resource1" (func $ctor)) + (alias export $host "[method]resource1.simple" (func $simple)) + (alias export $host "[method]resource1.take-borrow" (func $take-borrow)) + (alias export $host "[method]resource1.take-own" (func $take-own)) + + (core func $drop (canon resource.drop $r)) + (core func $ctor (canon lower (func $ctor))) + (core func $simple (canon lower (func $simple))) + (core func $take-own (canon lower (func $take-own))) + (core func $take-borrow (canon lower (func $take-borrow))) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (import "" "ctor" (func $ctor (param i32) (result i32))) + (import "" "simple" (func $simple (param i32 i32))) + (import "" "take-own" (func $take-own (param i32 i32))) + (import "" "take-borrow" (func $take-borrow (param i32 i32))) + + + (func $start + (local $r1 i32) + (local $r2 i32) + (local.set $r1 (call $ctor (i32.const 100))) + (local.set $r2 (call $ctor (i32.const 200))) + + (call $simple (local.get $r1) (i32.const 100)) + (call $simple (local.get $r1) (i32.const 100)) + (call $simple (local.get $r2) (i32.const 200)) + (call $simple (local.get $r1) (i32.const 100)) + (call $simple (local.get $r2) (i32.const 200)) + (call $simple (local.get $r2) (i32.const 200)) + + (call $drop (local.get $r1)) + (call $drop (local.get $r2)) + + + (local.set $r1 (call $ctor (i32.const 200))) + (local.set $r2 (call $ctor (i32.const 300))) + (call $take-borrow (local.get $r1) (local.get $r2)) + (call $take-borrow (local.get $r2) (local.get $r1)) + (call $take-borrow (local.get $r1) (local.get $r1)) + (call $take-borrow (local.get $r2) (local.get $r2)) + + (call $take-own (local.get $r1) (call $ctor (i32.const 400))) + (call $take-own (local.get $r2) (call $ctor (i32.const 500))) + (call $take-own (local.get $r2) (local.get $r1)) + (call $drop (local.get $r2)) + + ;; table should be empty at this point, so a fresh allocation should get + ;; index 0 + (if (i32.ne (call $ctor (i32.const 600)) (i32.const 0)) (unreachable)) + ) + + (start $start) + ) + (core instance (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + (export "ctor" (func $ctor)) + (export "simple" (func $simple)) + (export "take-own" (func $take-own)) + (export "take-borrow" (func $take-borrow)) + )) + )) +) + +;; Cannot pass out an owned resource when it's borrowed by the same call +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[constructor]resource1" (func (param "r" u32) (result (own $r)))) + (export "[method]resource1.take-own" (func (param "self" (borrow $r)) (param "b" (own $r)))) + )) + + (alias export $host "resource1" (type $r)) + (alias export $host "[constructor]resource1" (func $ctor)) + (alias export $host "[method]resource1.take-own" (func $take-own)) + + (core func $drop (canon resource.drop $r)) + (core func $ctor (canon lower (func $ctor))) + (core func $take-own (canon lower (func $take-own))) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (import "" "ctor" (func $ctor (param i32) (result i32))) + (import "" "take-own" (func $take-own (param i32 i32))) + + + (func (export "f") + (local $r i32) + (local.set $r (call $ctor (i32.const 100))) + (call $take-own (local.get $r) (local.get $r)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + (export "ctor" (func $ctor)) + (export "take-own" (func $take-own)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) +) + +(assert_trap (invoke "f") "cannot remove owned resource while borrowed") + +;; Borrows must actually exist +(component + (import "host" (instance $host + (export $r "resource1" (type (sub resource))) + (export "[method]resource1.simple" (func (param "self" (borrow $r)) (param "b" u32))) + )) + + (alias export $host "resource1" (type $r)) + (alias export $host "[method]resource1.simple" (func $simple)) + + (core func $drop (canon resource.drop $r)) + (core func $simple (canon lower (func $simple))) + + (core module $m + (import "" "drop" (func $drop (param i32))) + (import "" "simple" (func $simple (param i32 i32))) + + + (func (export "f") + (call $simple (i32.const 0) (i32.const 0)) + ) + ) + (core instance $i (instantiate $m + (with "" (instance + (export "drop" (func $drop)) + (export "simple" (func $simple)) + )) + )) + + (func (export "f") (canon lift (core func $i "f"))) +) + +(assert_trap (invoke "f") "unknown handle index 0")