From 171f24a352dc1af9c5d57ac0b5137b4e7e27e078 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 14:46:17 -0700 Subject: [PATCH 01/47] Fix signatures registered with modules-in-components This commit fixes a minor issue in `FunctionIndices::link_and_append_code` which previously ended up only filling out the `wasm_to_native_trampolines` field for the first module rather than all the modules. Additionally the first module might have too many entries that encompass all modules instead of just its own entries. The fix in this commit is to refactor this logic to ensure that the necessary maps are present for all translations. While technically a bug that can be surfaced through the embedder API it's pretty obscure. The given test here panics beforehand but succeeds afterwards, but this is moreso prep for some future resource-related work where this map will need persisting into the component metadata side of things. --- crates/wasmtime/src/compiler.rs | 33 +++++++++++++++++++++----------- tests/all/component_model/aot.rs | 21 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index e9ec3cb363fb..eda0f79cec4e 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -24,7 +24,7 @@ use crate::Engine; use anyhow::Result; -use std::collections::{btree_map, BTreeMap}; +use std::collections::{btree_map, BTreeMap, BTreeSet}; use std::{any::Any, collections::HashMap}; use wasmtime_environ::{ Compiler, DefinedFuncIndex, FuncIndex, FunctionBodyData, FunctionLoc, ModuleTranslation, @@ -586,6 +586,13 @@ impl FunctionIndices { .remove(&CompileKey::NATIVE_TO_WASM_TRAMPOLINE_KIND) .unwrap_or_default(); + // NB: unlike the above maps this is not emptied out during iteration + // since each module may reach into different portions of this map. + let wasm_to_native_trampolines = self + .indices + .remove(&CompileKey::WASM_TO_NATIVE_TRAMPOLINE_KIND) + .unwrap_or_default(); + artifacts.modules = translations .into_iter() .map(|(module, translation)| { @@ -619,16 +626,20 @@ impl FunctionIndices { }) .collect(); - let wasm_to_native_trampolines: Vec<(SignatureIndex, FunctionLoc)> = self - .indices - .remove(&CompileKey::WASM_TO_NATIVE_TRAMPOLINE_KIND) - .into_iter() - .flat_map(|x| x) - .map(|(key, i)| { - ( - SignatureIndex::from_u32(key.index), - symbol_ids_and_locs[i.unwrap_function()].1, - ) + let unique_and_sorted_sigs = translation + .module + .types + .iter() + .map(|(_, ty)| match ty { + ModuleType::Function(ty) => *ty, + }) + .collect::>(); + let wasm_to_native_trampolines = unique_and_sorted_sigs + .iter() + .map(|idx| { + let key = CompileKey::wasm_to_native_trampoline(*idx); + let compiled = wasm_to_native_trampolines[&key]; + (*idx, symbol_ids_and_locs[compiled.unwrap_function()].1) }) .collect(); diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs index 3b03aef23755..df6e0b66318c 100644 --- a/tests/all/component_model/aot.rs +++ b/tests/all/component_model/aot.rs @@ -98,3 +98,24 @@ fn cannot_serialize_exported_module() -> Result<()> { assert!(module.serialize().is_err()); Ok(()) } + +#[test] +fn usable_exported_modules() -> Result<()> { + let engine = super::engine(); + let component = Component::new( + &engine, + r#"(component + (core module $m) + (core module $m1 (export "a") + (import "" "" (func (param i32))) + ) + )"#, + )?; + let mut store = Store::new(&engine, ()); + let instance = Linker::new(&engine).instantiate(&mut store, &component)?; + let module = instance.get_module(&mut store, "a").unwrap(); + let mut core_linker = wasmtime::Linker::new(&engine); + core_linker.func_wrap("", "", |_: u32| {})?; + core_linker.instantiate(&mut store, &module)?; + Ok(()) +} From 92560e695e70b26d8331425ca17119e2d39508f0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 25 Apr 2023 12:40:52 -0700 Subject: [PATCH 02/47] Initial support for resources Lots of bits and pieces squashed into this commit. Much to be done still. --- Cargo.lock | 136 +++- Cargo.toml | 9 + cranelift/wasm/src/sections_translator.rs | 2 +- crates/component-macro/src/component.rs | 4 +- crates/cranelift/src/compiler/component.rs | 312 ++++++++- crates/environ/Cargo.toml | 2 +- crates/environ/src/component.rs | 12 + crates/environ/src/component/compiler.rs | 28 +- crates/environ/src/component/dfg.rs | 155 +++- crates/environ/src/component/info.rs | 123 +++- crates/environ/src/component/translate.rs | 180 +++-- .../environ/src/component/translate/adapt.rs | 8 +- .../environ/src/component/translate/inline.rs | 328 +++++---- crates/environ/src/component/types.rs | 139 ++-- .../environ/src/component/types/resources.rs | 82 +++ .../src/component/vmcomponent_offsets.rs | 79 ++- crates/environ/src/fact/trampoline.rs | 39 ++ crates/environ/src/module_environ.rs | 2 +- crates/misc/component-test-util/src/lib.rs | 4 +- crates/runtime/Cargo.toml | 2 +- crates/runtime/src/component.rs | 242 ++++++- crates/runtime/src/component/resources.rs | 86 +++ crates/runtime/src/component/transcode.rs | 110 ++- crates/wasmtime/Cargo.toml | 2 +- crates/wasmtime/src/compiler.rs | 133 +++- crates/wasmtime/src/component/component.rs | 68 +- crates/wasmtime/src/component/func.rs | 33 +- crates/wasmtime/src/component/func/host.rs | 35 +- crates/wasmtime/src/component/func/options.rs | 72 +- crates/wasmtime/src/component/func/typed.rs | 94 +-- crates/wasmtime/src/component/instance.rs | 181 ++++- crates/wasmtime/src/component/linker.rs | 21 +- crates/wasmtime/src/component/matching.rs | 97 ++- crates/wasmtime/src/component/mod.rs | 5 +- crates/wasmtime/src/component/resources.rs | 240 +++++++ crates/wasmtime/src/component/types.rs | 5 + crates/wasmtime/src/component/values.rs | 6 + crates/wasmtime/src/store/data.rs | 2 +- crates/wast/src/spectest.rs | 22 + tests/all/component_model.rs | 1 + tests/all/component_model/func.rs | 7 +- tests/all/component_model/resources.rs | 382 ++++++++++ .../component-model/resources.wast | 663 ++++++++++++++++++ winch/codegen/src/codegen/env.rs | 3 +- 44 files changed, 3635 insertions(+), 521 deletions(-) create mode 100644 crates/environ/src/component/types/resources.rs create mode 100644 crates/runtime/src/component/resources.rs create mode 100644 crates/wasmtime/src/component/resources.rs create mode 100644 tests/all/component_model/resources.rs create mode 100644 tests/misc_testsuite/component-model/resources.wast diff --git a/Cargo.lock b/Cargo.lock index f90a90431b30..e049a1759558 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,7 +469,7 @@ dependencies = [ "bitflags 1.3.2", "clap_derive", "clap_lex", - "indexmap", + "indexmap 1.9.1", "once_cell", "strsim", "termcolor", @@ -1162,7 +1162,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05a6c0bbc92278f84e742f08c0ab9cb16a987376cd2bc39d228ef9c74d98d6f7" dependencies = [ - "indexmap", + "indexmap 1.9.1", "instant", "log", "once_cell", @@ -1233,6 +1233,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "errno" version = "0.3.1" @@ -1486,7 +1492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 1.9.1", "stable_deref_trait", ] @@ -1519,7 +1525,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.1", "slab", "tokio", "tokio-util", @@ -1547,6 +1553,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.0" @@ -1710,6 +1722,17 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", + "serde", +] + [[package]] name = "indexmap-nostd" version = "0.4.0" @@ -2105,7 +2128,7 @@ checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "crc32fast", "hashbrown 0.13.2", - "indexmap", + "indexmap 1.9.1", "memchr", ] @@ -2469,6 +2492,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3682,6 +3716,14 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.29.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-metadata" version = "0.8.0" @@ -3689,9 +3731,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" dependencies = [ "anyhow", - "indexmap", + "indexmap 1.9.1", + "serde", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.107.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.8.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +dependencies = [ + "anyhow", + "indexmap 2.0.0", "serde", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", "wasmparser 0.107.0", ] @@ -3705,7 +3759,7 @@ dependencies = [ "log", "rand 0.8.5", "thiserror", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.107.0", ] @@ -3717,9 +3771,9 @@ checksum = "027ec1c470cd5d56c43b8e02040250b136ddb5975dd76a1c16915137f5f17e76" dependencies = [ "arbitrary", "flagset", - "indexmap", + "indexmap 1.9.1", "leb128", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.107.0", ] @@ -3767,16 +3821,15 @@ version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da34cec2a8c23db906cdf8b26e988d7a7f0d549eb5d51299129647af61a1b37" dependencies = [ - "indexmap", + "indexmap 1.9.1", ] [[package]] name = "wasmparser" version = "0.107.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" dependencies = [ - "indexmap", + "indexmap 2.0.0", "semver", ] @@ -3792,8 +3845,7 @@ dependencies = [ [[package]] name = "wasmprinter" version = "0.2.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc960b30b84abca377768f3c62cff3a1c74db8c0f6759ed581827da0bd3a3fed" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" dependencies = [ "anyhow", "wasmparser 0.107.0", @@ -3810,7 +3862,7 @@ dependencies = [ "cfg-if", "encoding_rs", "fxprof-processed-profile", - "indexmap", + "indexmap 2.0.0", "libc", "log", "object", @@ -3938,7 +3990,7 @@ dependencies = [ "test-programs", "tokio", "walkdir", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.107.0", "wasmtime", "wasmtime-cache", @@ -3984,7 +4036,7 @@ dependencies = [ "wasmtime", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser", + "wit-parser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4036,13 +4088,13 @@ dependencies = [ "cranelift-entity", "env_logger 0.10.0", "gimli", - "indexmap", + "indexmap 2.0.0", "log", "object", "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.107.0", "wasmprinter", "wasmtime-component-util", @@ -4132,7 +4184,7 @@ dependencies = [ "target-lexicon", "tempfile", "v8", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-mutate", "wasm-smith", "wasm-spec-interpreter", @@ -4194,7 +4246,7 @@ dependencies = [ "cc", "cfg-if", "encoding_rs", - "indexmap", + "indexmap 2.0.0", "libc", "log", "mach", @@ -4330,7 +4382,7 @@ version = "12.0.0" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4351,7 +4403,7 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.29.0", + "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4701,7 +4753,7 @@ checksum = "34a19aa69c4f33cb5ac10e55880a899f4d52ec85d4cde4d593b575e7a97e2b08" dependencies = [ "anyhow", "wit-component", - "wit-parser", + "wit-parser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4711,7 +4763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a50274c0cf2f8e33fc967825cef0114cdfe222d474c1d78aa77a6a801abaadf" dependencies = [ "heck", - "wasm-metadata", + "wasm-metadata 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "wit-bindgen-core", "wit-bindgen-rust-lib", "wit-component", @@ -4744,17 +4796,16 @@ dependencies = [ [[package]] name = "wit-component" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" dependencies = [ "anyhow", "bitflags 1.3.2", - "indexmap", + "indexmap 2.0.0", "log", - "wasm-encoder 0.29.0", - "wasm-metadata", + "wasm-encoder 0.29.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", + "wasm-metadata 0.8.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", "wasmparser 0.107.0", - "wit-parser", + "wit-parser 0.8.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", ] [[package]] @@ -4765,9 +4816,24 @@ checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 1.9.1", + "log", + "pulldown-cmark 0.8.0", + "semver", + "unicode-xid", + "url", +] + +[[package]] +name = "wit-parser" +version = "0.8.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.0.0", "log", - "pulldown-cmark", + "pulldown-cmark 0.9.3", "semver", "unicode-xid", "url", diff --git a/Cargo.toml b/Cargo.toml index 42cacff034ad..b9d0aaf66bc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -239,6 +239,7 @@ tempfile = "3.1.0" filecheck = "0.5.0" libc = "0.2.60" file-per-thread-logger = "0.2.0" +indexmap = "2.0.0" [features] default = [ @@ -310,3 +311,11 @@ debug-assertions = false # Omit integer overflow checks, which include failure messages which require # string initializers. overflow-checks = false + +[patch.crates-io] +wasmparser = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wasmprinter = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wit-component = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +# wasmparser = { path = '../wasm-tools/crates/wasmparser' } +# wasmprinter = { path = '../wasm-tools/crates/wasmprinter' } +# wit-component = { path = '../wasm-tools/crates/wit-component' } diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index e7b0486ea6ce..73e9f945a22c 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -56,7 +56,7 @@ pub fn parse_type_section<'a>( let ty = environ.convert_func_type(&wasm_func_ty); environ.declare_type_func(ty)?; } - Type::Array(_) => { + Type::Array(_) | Type::Struct(_) => { unimplemented!("gc proposal"); } } diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs index e1078b6fa96c..ae724ada15bc 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]) } @@ -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]) } diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 22a2c3d24788..2fe9087bb4a7 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -7,11 +7,8 @@ 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_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,141 @@ 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 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 `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); + builder.func.dfg.inst_results(call); + self.abi_store_results(&mut builder, ty, block0, &[], abi); + + 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 +383,84 @@ impl Compiler { }) } + fn load_libcall( + &self, + builder: &mut FunctionBuilder<'_>, + offsets: &VMComponentOffsets, + vmctx: ir::Value, + offset: u32, + ) -> ir::Value { + let pointer_type = self.isa.pointer_type(); + // Load the host function pointer for this transcode which comes from a + // function pointer within the VMComponentContext's libcall array. + let libcalls_array = builder.ins().load( + pointer_type, + MemFlags::trusted(), + vmctx, + i32::try_from(offsets.libcalls()).unwrap(), + ); + builder.ins().load( + pointer_type, + MemFlags::trusted(), + 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 { + Abi::Wasm | Abi::Native => block0_params, + Abi::Array => { + // After the host function has returned the results are loaded from + // `values_vec_ptr` and then returned. + 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 { + Abi::Wasm | Abi::Native => { + builder.ins().return_(results); + } + Abi::Array => { + // After the host function has returned the results are loaded from + // `values_vec_ptr` and then returned. + 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 +518,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 +587,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 +674,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( @@ -531,7 +758,7 @@ mod host { use cranelift_codegen::ir::{self, AbiParam}; use cranelift_codegen::isa::CallConv; - macro_rules! host_transcode { + macro_rules! define { ( $( $( #[$attr:meta] )* @@ -542,10 +769,10 @@ mod host { pub(super) fn $name(compiler: &Compiler, func: &mut ir::Function) -> (ir::SigRef, u32) { let pointer_type = compiler.isa.pointer_type(); let mut params = vec![ - $( AbiParam::new(host_transcode!(@ty pointer_type $param)) ),* + $( AbiParam::new(define!(@ty pointer_type $param)) ),* ]; let mut returns = Vec::new(); - $(host_transcode!(@push_return pointer_type params returns $result);)? + $(define!(@push_return pointer_type params returns $result);)? let sig = func.import_signature(ir::Signature { params, returns, @@ -560,15 +787,19 @@ mod host { (@ty $ptr:ident size) => ($ptr); (@ty $ptr:ident ptr_u8) => ($ptr); (@ty $ptr:ident ptr_u16) => ($ptr); + (@ty $ptr:ident u32) => (ir::types::I32); + (@ty $ptr:ident vmctx) => ($ptr); (@push_return $ptr:ident $params:ident $returns:ident size) => ($returns.push(AbiParam::new($ptr));); + (@push_return $ptr:ident $params:ident $returns:ident u32) => ($returns.push(AbiParam::new(ir::types::I32));); (@push_return $ptr:ident $params:ident $returns:ident size_pair) => ({ $params.push(AbiParam::new($ptr)); $returns.push(AbiParam::new($ptr)); }); } - wasmtime_environ::foreach_transcoder!(host_transcode); + wasmtime_environ::foreach_transcoder!(define); + wasmtime_environ::foreach_builtin_component_function!(define); mod offsets { macro_rules! offsets { @@ -581,13 +812,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/Cargo.toml b/crates/environ/Cargo.toml index 8eb2841519ab..ed0499b3038e 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -15,7 +15,7 @@ anyhow = { workspace = true } cranelift-entity = { workspace = true } wasmtime-types = { workspace = true } wasmparser = { workspace = true } -indexmap = { version = "1.0.2", features = ["serde-1"] } +indexmap = { workspace = true, features = ["serde"] } thiserror = { workspace = true } serde = { version = "1.0.94", features = ["derive"] } log = { workspace = true } diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index 0961b356e9a1..6947fb18914b 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -70,3 +70,15 @@ macro_rules! foreach_transcoder { } }; } + +/// TODO +#[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; + resource_drop(vmctx: vmctx, resource: u32, idx: u32); + } + }; +} diff --git a/crates/environ/src/component/compiler.rs b/crates/environ/src/component/compiler.rs index d03706851154..8f5e65ac6a58 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,28 @@ pub trait ComponentCompiler: Send + Sync { transcoder: &Transcoder, types: &ComponentTypes, ) -> Result>>; + + /// TODO + fn compile_resource_new( + &self, + component: &Component, + resource: &ResourceNew, + types: &ComponentTypes, + ) -> Result>>; + + /// TODO + fn compile_resource_rep( + &self, + component: &Component, + resource: &ResourceRep, + types: &ComponentTypes, + ) -> Result>>; + + /// TODO + 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..622178148253 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,15 @@ pub struct ComponentDfg { /// as the core wasm index of the export corresponding to the lowered /// version of the adapter. pub adapter_paritionings: PrimaryMap, + + /// TODO + pub resources: PrimaryMap, + + /// TODO + pub imported_resources: PrimaryMap, + + /// TODO + pub num_resource_tables: usize, } macro_rules! id { @@ -165,6 +174,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 +226,7 @@ pub struct LowerImport { pub import: RuntimeImportIndex, pub canonical_abi: SignatureIndex, pub options: CanonicalOptions, + pub lower_ty: TypeFuncIndex, } /// Same as `info::CanonicalOptions` @@ -238,6 +252,13 @@ pub struct Transcoder { pub signature: SignatureIndex, } +/// Same as `info::Resource` +#[allow(missing_docs)] +pub struct Resource { + pub rep: WasmType, + pub dtor: Option, +} + /// A helper structure to "intern" and deduplicate values of type `V` with an /// identifying key `K`. /// @@ -310,6 +331,10 @@ impl ComponentDfg { runtime_always_trap: Default::default(), runtime_lowerings: Default::default(), runtime_transcoders: Default::default(), + // runtime_resources: 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, @@ -342,12 +367,37 @@ 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, + + // runtime_resources: { + // let mut list = linearize + // .runtime_resources + // .iter() + // .map(|(id, idx)| (*idx, *id)) + // .collect::>(); + // list.sort_by_key(|(idx, _)| *idx); + // let mut runtime_resources = PrimaryMap::new(); + // for (idx, id) in list { + // let ty = self.resources[id].ty; + // let idx2 = runtime_resources.push(ty); + // assert_eq!(idx, idx2); + // } + // runtime_resources + // }, imports: self.imports, import_types: self.import_types, num_runtime_component_instances: self.num_runtime_component_instances, + num_resource_tables: self.num_resource_tables, + imported_resources: self.imported_resources, } } + + /// TODO + 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 +410,10 @@ struct LinearizeDfg<'a> { runtime_always_trap: HashMap, runtime_lowerings: HashMap, runtime_transcoders: HashMap, + // runtime_resources: HashSet, + runtime_resource_new: HashMap, + runtime_resource_rep: HashMap, + runtime_resource_drop: HashMap, } #[derive(Copy, Clone, Hash, Eq, PartialEq)] @@ -468,6 +522,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 +551,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 +594,93 @@ 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 resource(&mut self, id: TypeResourceTableIndex) -> TypeResourceTableIndex { + // if self.runtime_resources.insert(id) { + // let dtor = + // self.initializers + // .push(GlobalInitializer::Resource(info::Resource { + // index, + // dtor, + // rep, + // // ty, + // })); + // } + // id + // // let ret = self.intern( + // // id, + // // |me| &mut me.runtime_resources, + // // |me, id| { + // // let info = &me.dfg.resources[id]; + // // ( + // // info.dtor.as_ref().map(|i| me.core_def(i)), + // // info.rep, + // // info.ty, + // // ) + // // }, + // // |index, (dtor, rep, ty)| { + // // }, + // // ); + // // assert_eq!(id, ret); + // // ret + // } + 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..f58409102a38 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,28 @@ pub struct Component { /// The number of host transcoder functions needed for strings in adapter /// modules. pub num_transcoders: u32, + + /// TODO + pub num_resource_new: u32, + /// TODO + pub num_resource_rep: u32, + /// TODO + pub num_resource_drop: u32, + /// TODO + pub num_resource_tables: usize, + /// TODO + pub imported_resources: PrimaryMap, + // /// TODO + // pub runtime_resources: 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 + /// TODO + 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)) } } @@ -221,6 +228,15 @@ pub enum GlobalInitializer { /// needs to be initialized for a transcoder function and this will later be /// used to instantiate an adapter module. Transcoder(Transcoder), + + /// TODO + Resource(Resource), + /// TODO + ResourceNew(ResourceNew), + /// TODO + ResourceRep(ResourceRep), + /// TODO + ResourceDrop(ResourceDrop), } /// Metadata for extraction of a memory of what's being extracted and where it's @@ -288,6 +304,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 +367,13 @@ pub enum CoreDef { /// This refers to a cranelift-generated trampoline which calls to a /// host-defined transcoding function. Transcoder(RuntimeTranscoderIndex), + + /// TODO + ResourceNew(RuntimeResourceNewIndex), + /// TODO + ResourceRep(RuntimeResourceRepIndex), + /// TODO + ResourceDrop(RuntimeResourceDropIndex), } impl From> for CoreDef @@ -516,3 +543,73 @@ impl Transcoder { } pub use crate::fact::{FixedEncoding, Transcode}; + +/// TODO +#[derive(Debug, Serialize, Deserialize)] +pub struct Resource { + /// TODO + pub index: DefinedResourceIndex, + /// TODO + pub rep: WasmType, + /// TODO + pub dtor: Option, + // /// TODO + // pub ty: TypeResourceIndex, +} + +/// TODO +#[derive(Debug, Serialize, Deserialize)] +pub struct ResourceNew { + /// TODO + pub index: RuntimeResourceNewIndex, + /// TODO + pub resource: TypeResourceTableIndex, + /// TODO + pub signature: SignatureIndex, +} + +impl ResourceNew { + /// TODO + pub fn symbol_name(&self) -> String { + let resource = self.resource.as_u32(); + format!("wasm_component_resource_new{resource}") + } +} + +/// TODO +#[derive(Debug, Serialize, Deserialize)] +pub struct ResourceRep { + /// TODO + pub index: RuntimeResourceRepIndex, + /// TODO + pub resource: TypeResourceTableIndex, + /// TODO + pub signature: SignatureIndex, +} + +impl ResourceRep { + /// TODO + pub fn symbol_name(&self) -> String { + let resource = self.resource.as_u32(); + format!("wasm_component_resource_rep{resource}") + } +} + +/// TODO +#[derive(Debug, Serialize, Deserialize)] +pub struct ResourceDrop { + /// TODO + pub index: RuntimeResourceDropIndex, + /// TODO + pub resource: TypeResourceTableIndex, + /// TODO + pub signature: SignatureIndex, +} + +impl ResourceDrop { + /// TODO + 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 84c3ad63b3d5..1f5d4611ff2a 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::{self, 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,30 @@ 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, + /// TODO + types: Option, } +// TODO: comment about how this uses wasmparser type information #[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 +185,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 +257,7 @@ impl<'a, 'data> Translator<'a, 'data> { result: Translation::default(), tunables, validator, - types, + types: PreInliningComponentTypes { types }, parser: Parser::new(0), lexical_scopes: Vec::new(), static_components: Default::default(), @@ -324,7 +331,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, &self.result, &self.static_modules, &self.static_components, @@ -355,7 +362,7 @@ impl<'a, 'data> Translator<'a, 'data> { } Payload::End(offset) => { - self.validator.end(offset)?; + 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 +393,33 @@ 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)?; + 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 + .id_from_type_index(component_type_index, false) + .unwrap(); + 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 +436,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,61 +445,70 @@ 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 - .type_at(type_index, false) - .unwrap() - .as_component_func_type() - .unwrap(); - let ty = self.types.convert_component_func_type(types, ty)?; + let ty = types.id_from_type_index(type_index, false).unwrap(); 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).unwrap(); - 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).unwrap(); - 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.id_from_type_index(resource, false).unwrap(); + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceNew(resource, ty) } - } + wasmparser::CanonicalFunction::ResourceDrop { ty } => { + let ty = match ty { + wasmparser::ComponentValType::Type(t) => { + types.id_from_type_index(t, false).unwrap() + } + wasmparser::ComponentValType::Primitive(_) => unreachable!(), + }; + let resource = match types.type_from_id(ty) { + Some(types::Type::Defined( + types::ComponentDefinedType::Own(id) + | types::ComponentDefinedType::Borrow(id), + )) => *id, + _ => unreachable!(), + }; + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceDrop(resource, ty) + } + wasmparser::CanonicalFunction::ResourceRep { resource } => { + let resource = types.id_from_type_index(resource, false).unwrap(); + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceRep(resource, ty) + } + }; + self.result.initializers.push(init); } } @@ -531,6 +572,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? { @@ -538,14 +580,17 @@ impl<'a, 'data> Translator<'a, 'data> { component_index, args, } => { + let types = self.validator.types(0).unwrap(); + let ty = types.component_instance_at(index).unwrap(); 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; } } @@ -679,6 +724,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 { @@ -686,7 +732,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 @@ -732,7 +778,6 @@ impl<'a, 'data> Translator<'a, 'data> { wasmparser::ComponentExternalKind::Type => { let types = self.validator.types(0).unwrap(); let ty = types.id_from_type_index(index, false).unwrap(); - let ty = self.types.convert_type(types, ty)?; ComponentItem::Type(ty) } }) @@ -745,10 +790,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), @@ -765,8 +807,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 @@ -840,4 +882,34 @@ 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).unwrap(); + let ty = types.type_from_id(id).unwrap().as_func_type().unwrap(); + let ty = self.types.convert_func_type(ty); + self.types.module_types_builder().wasm_func_type(ty) + } +} + +struct PreInliningComponentTypes<'a> { + types: &'a mut ComponentTypesBuilder, +} + +impl PreInliningComponentTypes<'_> { + fn module_types_builder(&mut self) -> &mut ModuleTypesBuilder { + self.types.module_types_builder() + } +} + +impl TypeConvert for PreInliningComponentTypes<'_> { + fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType { + self.types.lookup_heap_type(index) + } +} + +impl Translation<'_> { + fn types_ref(&self) -> wasmparser::types::TypesRef<'_> { + self.types.as_ref().unwrap().as_ref() + } } diff --git a/crates/environ/src/component/translate/adapt.rs b/crates/environ/src/component/translate/adapt.rs index e8eefdc1fe56..ac33b0ea95ab 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..58a802d582d2 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(), @@ -73,15 +73,36 @@ 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(); 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, }; + + // TODO: comment this + let index = inliner.result.import_types.next_key(); + types.resources_mut().register_component_entity_type( + result.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) + }, + ); + + let ty = types.convert_component_entity_type(result.types_ref(), ty)?; + + // Imports of types are not required to be specified by the host since + // it's just for type information within the component. Note that this + // doesn't cover resource imports, which are in fact significant. + if let TypeDef::Interface(_) = ty { + continue; + } let index = inliner .result .import_types @@ -96,28 +117,23 @@ pub(super) fn run( // 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 +202,9 @@ struct InlinerFrame<'a> { module_instances: PrimaryMap>, component_instances: PrimaryMap>, components: PrimaryMap>, + + /// TODO + instance_ty: Option, } /// "Closure state" for a component which is resolved from the `ClosedOverVars` @@ -218,7 +237,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. @@ -309,21 +328,26 @@ struct ComponentDef<'a> { impl<'a> Inliner<'a> { 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 // initializers are processed. Currently this is modeled as an infinite // loop which drives the top-most iterator of the `frames` stack // provided as an argument to this function. + // + // TODO: comments about resources_cache loop { - let frame = frames.last_mut().unwrap(); + let (frame, _) = frames.last_mut().unwrap(); 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 +362,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 +385,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 +401,21 @@ 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!(), - }, + // + // TODO: update comment + Import(name, ty) => { + let arg = &frame.args[name.as_str()]; + 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), + ); + frame.push_item(arg.clone()); + } // Lowering a component function to a core wasm function is // generally what "triggers compilation". Here various metadata is @@ -405,9 +427,11 @@ impl<'a> Inliner<'a> { func, options, canonical_abi, - lower_ty, + lower_ty: a, } => { - let options_lower = self.adapter_options(frame, options); + let lower_ty = + types.convert_component_func_type(frame.translation.types_ref(), *a)?; + 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 +444,7 @@ impl<'a> Inliner<'a> { canonical_abi: *canonical_abi, import, options, + lower_ty, }); dfg::CoreDef::Lowered(index) } @@ -493,7 +518,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 +532,38 @@ 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, }); } + Resource(ty, rep, dtor) => { + let idx = self.result.resources.push(dfg::Resource { + rep: *rep, + dtor: dtor.map(|i| frame.funcs[i].clone()), + }); + let idx = self.result.resource_index(idx); + types + .resources_mut() + .register_resource(frame.translation.types_ref(), *ty, idx); + } + 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 +595,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); @@ -606,7 +655,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 +666,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 +676,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 +727,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 +844,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 +863,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 +911,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 +953,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 +965,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 +992,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 +1003,7 @@ impl<'a> InlinerFrame<'a> { translation, closure, args, + instance_ty, initializers: translation.initializers.iter(), funcs: Default::default(), @@ -1009,15 +1019,49 @@ 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)?) + } + }) + } + + /// TODO + 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); + } + + // 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. + // + // TODO: update comment + ComponentItemDef::Type(_ty) => {} } } @@ -1034,6 +1078,25 @@ impl<'a> InlinerFrame<'a> { ClosedOverComponent::Upvar(i) => self.closure.components[i].clone(), } } + + // TODO: comment this method + 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 +1106,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 +1125,32 @@ 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) } + + fn lookup_resource(&self, path: &[&str], types: &ComponentTypes) -> ResourceIndex { + let mut cur = self.clone(); + for element in path.iter().copied() { + let instance = match cur { + ComponentItemDef::Instance(def) => def, + _ => unreachable!(), + }; + cur = match instance { + ComponentInstanceDef::Items(names) => names[element].clone(), + ComponentInstanceDef::Import(path, ty) => { + ComponentItemDef::from_import(path.push(element), types[ty].exports[element]) + .unwrap() + } + }; + } + 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 a54f523fa11a..2eaa21f6bf96 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -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 @@ -107,6 +110,17 @@ indices! { pub struct TypeResultIndex(u32); /// Index pointing to a list type in the component model. pub struct TypeListIndex(u32); + /// TODO + /// + /// TODO: this is not a great name, it's more of a "unique ID" and there + /// should be a separate pass, probably the dfg pass, which keeps track of + /// which resources actually need tables. Only those lifted or lowered + /// actually need tables, not literally all resources within a component. + pub struct TypeResourceTableIndex(u32); + /// TODO + pub struct ResourceIndex(u32); + /// TODO + pub struct DefinedResourceIndex(u32); // ======================================================================== // Index types used to identify modules and components during compilation. @@ -178,6 +192,13 @@ 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); + + /// TODO + pub struct RuntimeResourceNewIndex(u32); + /// TODO + pub struct RuntimeResourceDropIndex(u32); + /// TODO + pub struct RuntimeResourceRepIndex(u32); } // Reexport for convenience some core-wasm indices which are also used in the @@ -186,14 +207,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 +237,7 @@ pub struct ComponentTypes { unions: PrimaryMap, options: PrimaryMap, results: PrimaryMap, + resource_tables: PrimaryMap, module_types: ModuleTypes, } @@ -238,7 +260,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 @@ -283,6 +307,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 +347,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 +386,33 @@ impl ComponentTypesBuilder { &mut self.module_types } + /// TODO + pub fn num_resource_tables(&self) -> usize { + self.component_types.resource_tables.len() + } + + /// TODO + pub fn resources_mut(&mut self) -> &mut ResourcesBuilder { + &mut self.resources + } + + /// TODO + 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 + .type_from_id(id) + .unwrap() + .as_component_func_type() + .unwrap(); let params = ty .params .iter() @@ -406,19 +448,14 @@ impl ComponentTypesBuilder { TypeDef::ComponentInstance(self.convert_instance(types, id)?) } types::ComponentEntityType::Func(id) => { - let id = types - .type_from_id(id) - .unwrap() - .as_component_func_type() - .unwrap(); - 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.type_from_id(created).unwrap() { types::Type::Defined(_) => { TypeDef::Interface(self.defined_type(types, created)?) } + types::Type::Resource(_) => TypeDef::Resource(self.resource_id(types, created)), _ => bail!("unsupported type export"), } } @@ -439,13 +476,16 @@ 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::Func(_) | types::Type::Array(_) => { + types::Type::Instance(_) + | types::Type::Func(_) + | types::Type::Array(_) + | types::Type::Struct(_) => { unreachable!() } - types::Type::Resource(_) => unimplemented!(), + types::Type::Resource(_) => TypeDef::Resource(self.resource_id(types, id)), }) } @@ -454,9 +494,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.type_from_id(id).unwrap().as_component_type().unwrap(); let mut result = TypeComponent::default(); for (name, ty) in ty.imports.iter() { @@ -471,9 +508,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( @@ -481,9 +516,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 .type_from_id(id) .unwrap() @@ -496,9 +528,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( @@ -506,9 +536,6 @@ 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.type_from_id(id).unwrap().as_module_type().unwrap(); let mut result = TypeModule::default(); for ((module, field), ty) in ty.imports.iter() { @@ -522,9 +549,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( @@ -550,9 +575,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.type_from_id(id).unwrap().as_defined_type().unwrap() { types::ComponentDefinedType::Primitive(ty) => ty.into(), types::ComponentDefinedType::Record(e) => { @@ -576,19 +598,20 @@ 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) } - fn valtype( + /// TODO + pub fn valtype( &mut self, types: types::TypesRef<'_>, ty: &types::ComponentValType, @@ -746,6 +769,19 @@ impl ComponentTypesBuilder { Ok(self.add_list_type(TypeList { element })) } + /// TODO + pub fn resource_id( + &mut self, + types: types::TypesRef<'_>, + id: types::TypeId, + ) -> TypeResourceTableIndex { + let id = match types.type_from_id(id) { + Some(wasmparser::types::Type::Resource(id)) => *id, + _ => unreachable!(), + }; + 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) @@ -819,7 +855,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 } @@ -903,6 +941,8 @@ pub enum TypeDef { Module(TypeModuleIndex), /// A core wasm function using only core wasm types. CoreFunc(SignatureIndex), + /// TODO + Resource(TypeResourceTableIndex), } // NB: Note that maps below are stored as an `IndexMap` now but the order @@ -991,6 +1031,8 @@ pub enum InterfaceType { Union(TypeUnionIndex), Option(TypeOptionIndex), Result(TypeResultIndex), + Own(TypeResourceTableIndex), + Borrow(TypeResourceTableIndex), } impl From<&wasmparser::PrimitiveValType> for InterfaceType { @@ -1471,6 +1513,13 @@ pub struct TypeResult { pub info: VariantInfo, } +/// TODO +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeResourceTable { + /// TODO + pub ty: ResourceIndex, +} + /// 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..22f86e889d82 --- /dev/null +++ b/crates/environ/src/component/types/resources.rs @@ -0,0 +1,82 @@ +use crate::component::{ComponentTypes, ResourceIndex, TypeResourceTable, TypeResourceTableIndex}; +use std::collections::HashMap; +use wasmparser::types; + +/// TODO +/// TODO: this is a very special cache +#[derive(Default, Clone)] +pub struct ResourcesBuilder { + resource_id_to_table_index: HashMap, + resource_id_to_resource_index: HashMap, +} + +impl ResourcesBuilder { + /// TODO + 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]; + types.resource_tables.push(TypeResourceTable { ty }) + }) + } + + /// TODO + pub fn register_component_entity_type<'a>( + &mut self, + types: types::TypesRef<'a>, + ty: types::ComponentEntityType, + path: &mut Vec<&'a str>, + register: &mut dyn FnMut(&[&'a str]) -> ResourceIndex, + ) { + match ty { + types::ComponentEntityType::Instance(id) => { + let ty = types + .type_from_id(id) + .unwrap() + .as_component_instance_type() + .unwrap(); + for (name, ty) in ty.exports.iter() { + path.push(name); + self.register_component_entity_type(types, *ty, path, register); + path.pop(); + } + } + types::ComponentEntityType::Type { created, .. } => { + let id = match types.type_from_id(created).unwrap() { + types::Type::Resource(id) => *id, + _ => return, + }; + self.resource_id_to_resource_index + .entry(id) + .or_insert_with(|| register(path)); + } + + // TODO: comment why not needed + types::ComponentEntityType::Func(_) + | types::ComponentEntityType::Module(_) + | types::ComponentEntityType::Component(_) + | types::ComponentEntityType::Value(_) => {} + } + } + + /// TODO + pub fn register_resource<'a>( + &mut self, + types: types::TypesRef<'a>, + id: types::TypeId, + ty: ResourceIndex, + ) { + let id = match types.type_from_id(id).unwrap() { + types::Type::Resource(id) => *id, + _ => unreachable!(), + }; + let prev = self.resource_id_to_resource_index.insert(id, ty); + assert!(prev.is_none()); + } +} diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs index e8d52e8f51a9..dd4decb72534 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -2,23 +2,23 @@ // // 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], // } -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,16 +61,25 @@ pub struct VMComponentOffsets

{ pub num_always_trap: u32, /// Number of transcoders needed for string conversion. pub num_transcoders: u32, + /// TODO + pub num_resource_new: u32, + /// TODO + pub num_resource_rep: u32, + /// TODO + pub num_resource_drop: 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, @@ -100,14 +109,20 @@ 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, 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, @@ -142,7 +157,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,6 +166,9 @@ 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()), @@ -179,10 +197,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 +261,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 { diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index bd2b9cdc29f3..a6e278bcfc06 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -554,6 +554,9 @@ impl Compiler<'_, '_> { // 2 cases to consider for each of these variants. InterfaceType::Option(_) | InterfaceType::Result(_) => 2, + + // TODO - something nonzero, is 1 right? + InterfaceType::Own(_) | InterfaceType::Borrow(_) => 1, }; match self.fuel.checked_sub(cost) { @@ -587,6 +590,10 @@ 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), + InterfaceType::Own(t) => todo!(), + InterfaceType::Borrow(t) => todo!(), } } @@ -2447,6 +2454,38 @@ impl Compiler<'_, '_> { } } + // fn translate_own( + // &mut self, + // src_ty: ResourceId, + // src: &Source<'_>, + // dst_ty: &InterfaceType, + // dst: &Destination, + // ) { + // let dst_ty = match dst_ty { + // InterfaceType::Own(t) => t, + // _ => panic!("expected an `Own`"), + // }; + + // drop((src_ty, src, dst_ty, dst)); + // todo!(); + // } + + // fn translate_borrow( + // &mut self, + // src_ty: ResourceId, + // src: &Source<'_>, + // dst_ty: &InterfaceType, + // dst: &Destination, + // ) { + // let dst_ty = match dst_ty { + // InterfaceType::Borrow(t) => t, + // _ => panic!("expected an `Borrow`"), + // }; + + // drop((src_ty, src, dst_ty, dst)); + // todo!(); + // } + 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/module_environ.rs b/crates/environ/src/module_environ.rs index bfa81fd8b2ed..cb36343c3fba 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -243,7 +243,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { let ty = self.convert_func_type(&wasm_func_ty); self.declare_type_func(ty)?; } - Type::Array(_) => { + Type::Array(_) | Type::Struct(_) => { unimplemented!("gc proposal") } } diff --git a/crates/misc/component-test-util/src/lib.rs b/crates/misc/component-test-util/src/lib.rs index 46cdfb7964b0..e9d63c095294 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) } } diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 279bc0b72e3b..18afaaf91f50 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -18,7 +18,7 @@ wasmtime-jit-debug = { workspace = true, features = ["gdb_jit_int"] } libc = { version = "0.2.112", default-features = false } log = { workspace = true } memoffset = "0.8.0" -indexmap = "1.0.2" +indexmap = { workspace = true } cfg-if = { workspace = true } rand = { version = "0.8.3", features = ['small_rng'] } anyhow = { workspace = true } diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index fc9207f69b98..ace40ca41bf1 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -6,28 +6,27 @@ //! Eventually it's intended that module-to-module calls, which would be //! cranelift-compiled adapters, will use this `VMComponentContext` as well. +use self::resources::ResourceTables; 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::component::*; use wasmtime_environ::HostPtr; const INVALID_PTR: usize = 0xdead_dead_beef_beef_u64 as usize; +mod resources; mod transcode; /// Runtime representation of a component instance and all state necessary for @@ -49,6 +48,13 @@ pub struct ComponentInstance { /// Runtime type information about this component. runtime_info: Arc, + /// TODO + resource_tables: ResourceTables, + + /// TODO: + /// TODO: Any is bad + imported_resources: Box, + /// A zero-sized field which represents the end of the struct for the actual /// `VMComponentContext` to be allocated behind. vmctx: VMComponentContext, @@ -128,6 +134,18 @@ pub struct VMComponentContext { } impl ComponentInstance { + /// TODO + 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,6 +171,7 @@ impl ComponentInstance { alloc_size: usize, offsets: VMComponentOffsets, runtime_info: Arc, + imported_resources: Box, store: *mut dyn Store, ) { assert!(alloc_size >= Self::alloc_layout(&offsets).size()); @@ -170,7 +189,9 @@ impl ComponentInstance { ) .unwrap(), ), + resource_tables: ResourceTables::new(runtime_info.component().num_resource_tables), runtime_info, + imported_resources, vmctx: VMComponentContext { _marker: marker::PhantomPinned, }, @@ -292,6 +313,21 @@ impl ComponentInstance { unsafe { self.func_ref(self.offsets.transcoder_func_ref(idx)) } } + /// Same as `lowering_func_ref` except for the transcoding 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 transcoding 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 transcoding 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 +453,66 @@ impl ComponentInstance { } } + /// Same as `set_lowering` but for the transcoder 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 transcoder 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 transcoder 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, @@ -438,8 +534,8 @@ impl ComponentInstance { 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(); @@ -474,6 +570,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); @@ -492,10 +603,45 @@ impl ComponentInstance { } } + /// TODO + 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() } + + /// TODO + pub fn imported_resources(&self) -> &dyn Any { + &*self.imported_resources + } + + /// TODO + pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> u32 { + self.resource_tables.resource_new(resource, rep) + } + + /// TODO + pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + self.resource_tables.resource_lower_own(ty, rep) + } + + /// TODO + pub fn resource_lift_own(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { + self.resource_tables.resource_lift_own(ty, idx) + } + + /// TODO + pub fn resource_rep32(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result { + self.resource_tables.resource_rep(resource, idx) + } + + /// TODO + pub fn resource_drop(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result<()> { + self.resource_tables.resource_drop(resource, idx) + } } impl VMComponentContext { @@ -524,6 +670,7 @@ impl OwnedComponentInstance { /// heap with `malloc` and configures it for the `component` specified. pub fn new( runtime_info: Arc, + imported_resources: Box, store: *mut dyn Store, ) -> OwnedComponentInstance { let component = runtime_info.component(); @@ -541,7 +688,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, + imported_resources, + store, + ); let ptr = SendSyncPtr::new(ptr); OwnedComponentInstance { ptr } @@ -556,6 +710,11 @@ impl OwnedComponentInstance { &mut *self.ptr.as_ptr() } + /// TODO + 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 +785,71 @@ 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, + ) + } + } + + /// TODO + pub fn imported_resources_mut(&mut self) -> &mut dyn Any { + unsafe { &mut *(*self.ptr.as_ptr()).imported_resources } + } } impl Deref for OwnedComponentInstance { diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs new file mode 100644 index 000000000000..afdd5600178b --- /dev/null +++ b/crates/runtime/src/component/resources.rs @@ -0,0 +1,86 @@ +use anyhow::{bail, Result}; +use std::mem; +use wasmtime_environ::component::TypeResourceTableIndex; +use wasmtime_environ::PrimaryMap; + +pub struct ResourceTables { + tables: PrimaryMap, +} + +#[derive(Default)] +struct ResourceTable { + next: usize, + slots: Vec>, +} + +enum Slot { + Free { next: usize }, + Taken(T), +} + +impl ResourceTables { + pub fn new(amt: usize) -> ResourceTables { + let mut tables = PrimaryMap::with_capacity(amt); + for _ in 0..amt { + tables.push(ResourceTable::default()); + } + ResourceTables { tables } + } + + pub fn resource_new(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + self.tables[ty].insert(rep) + } + + pub fn resource_rep(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { + self.tables[ty].get(idx) + } + + pub fn resource_drop(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result<()> { + let rep = self.tables[ty].remove(idx)?; + Ok(()) + // TODO: how to run the dtor for `rep`? + } + + pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + // TODO: this impl should probably not be literally the same as `resource_new` + self.tables[ty].insert(rep) + } + + pub fn resource_lift_own(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { + self.tables[ty].remove(idx) + } +} + +impl ResourceTable { + pub fn insert(&mut self, rep: u32) -> u32 { + if self.next == self.slots.len() { + self.slots.push(Slot::Free { + next: self.next + 1, + }); + } + let ret = self.next; + self.next = match mem::replace(&mut self.slots[self.next], Slot::Taken(rep)) { + Slot::Free { next } => next, + Slot::Taken(_) => unreachable!(), + }; + u32::try_from(ret).unwrap() + } + + pub fn get(&mut self, idx: u32) -> Result { + match self.slots.get(idx as usize) { + Some(Slot::Taken(rep)) => Ok(*rep), + _ => bail!("unknown handle index {idx}"), + } + } + + pub fn remove(&mut self, idx: u32) -> Result { + let rep = match self.slots.get(idx as usize) { + Some(Slot::Taken(rep)) => *rep, + _ => bail!("unknown handle index {idx}"), + }; + // TODO: dtor called here + self.slots[idx as usize] = Slot::Free { next: self.next }; + self.next = idx as usize; + Ok(rep) + } +} diff --git a/crates/runtime/src/component/transcode.rs b/crates/runtime/src/component/transcode.rs index 5c007c8df1a7..89dea3c407bd 100644 --- a/crates/runtime/src/component/transcode.rs +++ b/crates/runtime/src/component/transcode.rs @@ -1,11 +1,72 @@ //! 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)] +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 u32) => (u32); + (@ty vmctx) => (*mut VMComponentContext); + + (@retptr size_pair) => (*mut usize); + (@retptr size) => (()); + (@retptr u32) => (()); +} + +/// TODO +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!(@retptr $result),)? + ) $( -> signature!(@ty $result))?, + )* + } + + impl VMComponentBuiltins { + pub const INIT: VMComponentBuiltins = VMComponentBuiltins { + $($name: trampolines::$name,)* + }; + } + }; + + (@ty vmctx) => (*mut VMComponentContext); + (@ty u32) => (u32); +} + +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,9 +89,9 @@ macro_rules! define_transcoders { pub struct VMBuiltinTranscodeArray { $( $name: unsafe extern "C" fn( - $(define_transcoders!(@ty $param),)* - $(define_transcoders!(@retptr $result),)? - ) $( -> define_transcoders!(@ty $result))?, + $(signature!(@ty $param),)* + $(signature!(@retptr $result),)? + ) $( -> signature!(@ty $result))?, )* } @@ -40,14 +101,6 @@ macro_rules! define_transcoders { }; } }; - - (@ty size) => (usize); - (@ty size_pair) => (usize); - (@ty ptr_u8) => (*mut u8); - (@ty ptr_u16) => (*mut u16); - - (@retptr size_pair) => (*mut usize); - (@retptr size) => (()); } wasmtime_environ::foreach_transcoder!(define_transcoders); @@ -58,7 +111,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] )* @@ -67,13 +122,13 @@ mod trampolines { ) => ( $( pub unsafe extern "C" fn $name( - $($pname : define_transcoders!(@ty $param),)* + $($pname : signature!(@ty $param),)* // If a result is given then a `size_pair` results gets its // second result value passed via a return pointer here, so // optionally indicate a return pointer. - $(_retptr: define_transcoders!(@retptr $result))? - ) $( -> define_transcoders!(@ty $result))? { - $(transcoders!(@validate_param $pname $param);)* + $(_retptr: signature!(@retptr $result))? + ) $( -> 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 @@ -85,7 +140,7 @@ mod trampolines { super::$name($($pname),*) }); match result { - Ok(Ok(ret)) => transcoders!(@convert_ret ret _retptr $($result)?), + Ok(Ok(ret)) => shims!(@convert_ret ret _retptr $($result)?), Ok(Err(err)) => crate::traphandlers::raise_trap( crate::traphandlers::TrapReason::User { error: err, @@ -100,6 +155,7 @@ mod trampolines { (@convert_ret $ret:ident $retptr:ident) => ($ret); (@convert_ret $ret:ident $retptr:ident size) => ($ret); + (@convert_ret $ret:ident $retptr:ident u32) => ($ret); (@convert_ret $ret:ident $retptr:ident size_pair) => ({ let (a, b) = $ret; *$retptr = b; @@ -115,7 +171,8 @@ mod trampolines { (@validate_param $arg:ident $ty:ident) => (); } - 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 @@ -449,3 +506,20 @@ 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| instance.resource_drop(resource, idx)) +} diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 26d89b0344b3..f5c045e4aeff 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -37,7 +37,7 @@ wat = { workspace = true, optional = true } serde = { version = "1.0.94", features = ["derive"] } serde_json = { workspace = true } bincode = "1.2.1" -indexmap = "1.6" +indexmap = { workspace = true } paste = "1.0.3" psm = "0.1.11" once_cell = { workspace = true } diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index eda0f79cec4e..9925b908df14 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -41,13 +41,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 @@ -57,10 +59,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 { @@ -97,9 +105,12 @@ 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); fn lowering(index: wasmtime_environ::component::LoweredIndex) -> Self { Self { @@ -121,6 +132,27 @@ 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(), + } + } } #[derive(Clone, Copy)] @@ -255,7 +287,49 @@ impl<'a> CompileInputs<'a> { }) }); } - wasmtime_environ::component::GlobalInitializer::InstantiateModule(_) + + wasmtime_environ::component::GlobalInitializer::ResourceNew(r) => { + push_input(&mut inputs, 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) => { + push_input(&mut inputs, 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) => { + push_input(&mut inputs, 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(_) => { @@ -670,6 +744,27 @@ 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(); } debug_assert!( @@ -701,6 +796,21 @@ 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, + >, } impl Artifacts { @@ -713,6 +823,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..eeee21629fae 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -9,8 +9,9 @@ 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}; @@ -73,6 +74,13 @@ struct CompiledComponentInfo { /// Where all the cranelift-generated transcode functions are located in the /// compiled image of this component. transcoders: PrimaryMap>, + + /// TODO + resource_new: PrimaryMap>, + /// TODO + resource_rep: PrimaryMap>, + /// TODO + resource_drop: PrimaryMap>, } pub(crate) struct AllCallFuncPointers { @@ -225,6 +233,9 @@ 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, }; let artifacts = ComponentArtifacts { info, @@ -303,12 +314,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 +331,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 { diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index a2258f0ae644..718a9b46db72 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -218,32 +218,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(store); + 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(()) @@ -461,12 +469,9 @@ impl Func { debug_assert!(flags.may_leave()); flags.set_may_leave(false); + let instance_ptr = instance.instance_ptr(); let result = lower( - &mut LowerContext { - store: store.as_context_mut(), - options: &options, - types: &types, - }, + &mut LowerContext::new(store.as_context_mut(), &options, &types, instance_ptr), params, InterfaceType::Tuple(types[ty].params), map_maybe_uninit!(space.params), @@ -507,11 +512,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, - }, + &LiftContext::new(store.0, &options, &types, instance_ptr), InterfaceType::Tuple(types[ty].results), ret, )?; diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs index 12fb60e7f604..556b2ae9d9e5 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")?; @@ -220,22 +221,14 @@ where } }; let params = storage.lift_params( - &LiftContext { - store: cx.0, - options: &options, - types, - }, + &LiftContext::new(cx.0, &options, types, instance), 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, - }, + &mut LowerContext::new(cx, &options, types, instance), result_tys, ret, )?; @@ -355,11 +348,7 @@ 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 cx = LiftContext::new(store.0, &options, types, instance); 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 = @@ -400,11 +389,7 @@ where Type::from(ty, types).check(val)?; } - let mut cx = LowerContext { - store, - options: &options, - types, - }; + let mut cx = LowerContext::new(store, &options, types, instance); 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()) { diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index c5472eb331cc..b6878ad6b14c 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -1,9 +1,12 @@ +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::ComponentInstance; use wasmtime_runtime::{VMFuncRef, VMMemoryDefinition}; /// Runtime representation of canonical ABI options in the component model. @@ -177,10 +180,32 @@ pub struct LowerContext<'a, T> { /// used for type lookups and general type queries during the /// lifting/lowering process. pub types: &'a ComponentTypes, + + /// TODO + instance: *mut ComponentInstance, } #[doc(hidden)] impl<'a, T> LowerContext<'a, T> { + /// TODO + /// + /// # Unsafety + /// + /// TODO: ptr must be valid + 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 +260,15 @@ impl<'a, T> LowerContext<'a, T> { .try_into() .unwrap() } + + /// TODO + pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + unsafe { (*self.instance).resource_lower_own(ty, rep) } + } + + pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { + InstanceType::new(self.store.0, unsafe { &*self.instance }).resource_type(ty) + } } /// Contextual information used when lifting a type from a component into the @@ -253,10 +287,31 @@ pub struct LiftContext<'a> { /// Instance type information, like with lowering. pub types: &'a Arc, + + instance: *mut ComponentInstance, } #[doc(hidden)] impl<'a> LiftContext<'a> { + /// TODO + /// + /// # Unsafety + /// + /// TODO: ptr must be valid + pub unsafe fn new( + store: &'a StoreOpaque, + options: &'a Options, + types: &'a Arc, + instance: *mut ComponentInstance, + ) -> LiftContext<'a> { + LiftContext { + store, + options, + types, + instance, + } + } + /// Returns the entire contents of linear memory for this set of lifting /// options. /// @@ -267,4 +322,19 @@ impl<'a> LiftContext<'a> { pub fn memory(&self) -> &'a [u8] { self.options.memory(self.store) } + + /// TODO + pub fn instance_ptr(&self) -> *mut ComponentInstance { + self.instance + } + + /// TODO + pub fn resource_lift_own(&self, ty: TypeResourceTableIndex, idx: u32) -> Result { + // TODO: document unsafe + unsafe { (*self.instance).resource_lift_own(ty, idx) } + } + + pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { + InstanceType::new(self.store, unsafe { &*self.instance }).resource_type(ty) + } } diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs index cf297f0ccf52..dfe840ac7875 100644 --- a/crates/wasmtime/src/component/func/typed.rs +++ b/crates/wasmtime/src/component/func/typed.rs @@ -1,18 +1,22 @@ 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`. @@ -431,7 +435,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)] @@ -554,7 +558,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) } } @@ -656,7 +660,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)) @@ -737,7 +741,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)) @@ -798,7 +802,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)), @@ -856,7 +860,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)), @@ -916,7 +920,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)), @@ -1190,7 +1194,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)), @@ -1227,9 +1231,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 +1334,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, } @@ -1356,6 +1361,7 @@ impl WasmList { options: *cx.options, elem, types: cx.types.clone(), + instance: SendSyncPtr::new(NonNull::new(cx.instance_ptr()).unwrap()), _marker: marker::PhantomData, }) } @@ -1375,19 +1381,17 @@ impl WasmList { // 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) + pub fn get(&self, mut store: impl AsContextMut, index: usize) -> Option> { + self.get_from_store(store.as_context_mut().0, index) } fn get_from_store(&self, store: &StoreOpaque, index: usize) -> Option> { if index >= self.len { return None; } - let cx = LiftContext { - store, - options: &self.options, - types: &self.types, - }; + // TODO: comment unsafety, validity of `self.instance` carried over + let cx = + unsafe { LiftContext::new(store, &self.options, &self.types, self.instance.as_ptr()) }; // 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 @@ -1404,7 +1408,7 @@ 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()) @@ -1469,7 +1473,7 @@ 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) } } @@ -1504,12 +1508,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 +1534,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 +1568,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 +1612,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 +1644,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 +1673,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 +1726,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)), } } @@ -1847,10 +1855,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 => {} @@ -2166,7 +2174,7 @@ macro_rules! impl_component_ty_for_tuples { fn typecheck( ty: &InterfaceType, - types: &ComponentTypes, + types: &InstanceType<'_>, ) -> Result<()> { typecheck_tuple(ty, types, &[$($t::typecheck),*]) } @@ -2260,7 +2268,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 +2293,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..f446668057a7 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,14 @@ 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, VMNativeCallFunction, VMSharedSignatureIndex, VMWasmCallFunction, +}; /// An instantiated component. /// @@ -133,6 +133,11 @@ impl Instance { .module(name) .cloned() } + + /// TODO + 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 +168,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 +218,17 @@ 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, store: &StoreOpaque) -> InstanceType<'_> { + InstanceType::new(store, self.instance()) + } } struct Instantiator<'a> { @@ -214,8 +242,11 @@ struct Instantiator<'a> { pub enum RuntimeImport { Func(Arc), Module(Module), + Resource(ResourceType, Arc), } +pub type ImportedResources = PrimaryMap; + impl<'a> Instantiator<'a> { fn new( component: &'a Component, @@ -224,6 +255,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 +264,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(), + Box::new(imported_resources), + store.traitobj(), + ), imports: imports.clone(), }, } @@ -239,6 +276,19 @@ impl<'a> Instantiator<'a> { fn run(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> { let env_component = self.component.env_component(); + let imported_resources = self + .data + .state + .imported_resources_mut() + .downcast_mut::() + .unwrap(); + for (idx, import) in env_component.imported_resources.iter() { + let i = imported_resources.push(match &self.imports[*import] { + RuntimeImport::Resource(ty, _dtor) => *ty, + _ => unreachable!(), + }); + assert_eq!(i, idx); + } for initializer in env_component.initializers.iter() { match initializer { GlobalInitializer::InstantiateModule(m) => { @@ -301,6 +351,11 @@ impl<'a> Instantiator<'a> { } GlobalInitializer::Transcoder(e) => self.transcoder(e), + + GlobalInitializer::Resource(r) => self.resource(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 +386,87 @@ 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, resource: &Resource) { + // TODO + } + + 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 +758,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 +774,19 @@ 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(self.store).resource_type(*id)) + } + Export::Type(_) + | Export::LiftedFunction { .. } + | Export::Module(_) + | 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..dcf4005a1568 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; @@ -57,6 +59,7 @@ pub 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,7 @@ 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(t.clone(), dtor.clone()), // This is guaranteed by the compilation process that "leaf" // runtime imports are never instances. @@ -367,6 +373,17 @@ impl LinkerInstance<'_, T> { self.insert(name, Definition::Module(module.clone())) } + /// TODO + pub fn resource( + &mut self, + name: &str, + dtor: impl Fn(&mut T, u32) + Send + Sync + 'static, + ) -> Result<()> { + let name = self.strings.intern(name); + let dtor = Arc::new(move |ptr: *mut u8, val| unsafe { dtor(&mut *ptr.cast(), val) }); + 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..e51488a3bfa4 100644 --- a/crates/wasmtime/src/component/matching.rs +++ b/crates/wasmtime/src/component/matching.rs @@ -1,20 +1,36 @@ use crate::component::func::HostFunc; +use crate::component::instance::ImportedResources; use crate::component::linker::{Definition, NameMap, Strings}; +use crate::component::ResourceType; +use crate::store::{StoreId, StoreOpaque}; use crate::types::matching; use crate::Module; use anyhow::{anyhow, bail, Context, Result}; 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: PrimaryMap, +} + +#[derive(Copy, Clone)] +#[doc(hidden)] +pub struct InstanceType<'a> { + pub instance: Option<(StoreId, &'a ComponentInstance)>, + pub types: &'a ComponentTypes, + pub imported_resources: &'a ImportedResources, } impl TypeChecker<'_> { - pub fn definition(&self, expected: &TypeDef, actual: Option<&Definition>) -> Result<()> { + pub 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 +48,49 @@ 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; + match actual { + Some(Definition::Resource(actual, _dtor)) => { + 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. + None => { + let id = self.imported_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(()) + } + + // If a resource is imported yet nothing was supplied then + // that's only successful if the resource has itself alredy 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() => Ok(()), + + _ => bail!("expected resource found {}", desc(actual)), + } + } + // not possible for valid components to import TypeDef::CoreFunc(_) => unreachable!(), } @@ -68,7 +127,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 +153,12 @@ impl TypeChecker<'_> { } fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> { - actual.typecheck(expected, self.types) + let instance_type = InstanceType { + types: self.types, + imported_resources: &self.imported_resources, + instance: None, + }; + actual.typecheck(expected, &instance_type) } } @@ -107,6 +175,27 @@ impl Definition { Definition::Module(_) => "module", Definition::Func(_) => "func", Definition::Instance(_) => "instance", + Definition::Resource(..) => "resource", + } + } +} + +impl<'a> InstanceType<'a> { + pub fn new(store: &StoreOpaque, instance: &'a ComponentInstance) -> InstanceType<'a> { + InstanceType { + instance: Some((store.id(), instance)), + types: instance.component_types(), + imported_resources: instance.imported_resources().downcast_ref().unwrap(), + } + } + + pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType { + let index = self.types[index].ty; + if let Some((store, instance)) = self.instance { + if let Some(index) = instance.component().defined_resource_index(index) { + return ResourceType::guest(store, instance, index); + } } + self.imported_resources[index] } } 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..8eb68739636a --- /dev/null +++ b/crates/wasmtime/src/component/resources.rs @@ -0,0 +1,240 @@ +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; +use anyhow::{bail, Result}; +use std::any::TypeId; +use std::cell::Cell; +use std::marker; +use std::mem::MaybeUninit; +use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType}; +use wasmtime_runtime::component::ComponentInstance; + +/// TODO +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ResourceType { + kind: ResourceTypeKind, +} + +impl ResourceType { + /// TODO + 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, + // TODO: comment this + instance: instance as *const _ as usize, + id, + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum ResourceTypeKind { + Host(TypeId), + Guest { + store: StoreId, + // TODO: comment what this `usize` is + instance: usize, + id: DefinedResourceIndex, + }, +} + +/// TODO +pub struct Resource { + rep: Cell>, + _marker: marker::PhantomData T>, +} + +impl Resource { + /// TODO + pub fn new(rep: u32) -> Resource { + Resource { + rep: Cell::new(Some(rep)), + _marker: marker::PhantomData, + } + } + + /// TODO - document panic + pub fn rep(&self) -> u32 { + match self.rep.get() { + Some(val) => val, + None => todo!(), + } + } + + fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { + let resource = match ty { + InterfaceType::Own(t) => t, + _ => bad_type_info(), + }; + let rep = match self.rep.replace(None) { + Some(rep) => rep, + None => bail!("resource already consumed"), + }; + Ok(cx.resource_lower_own(resource, rep)) + } + + fn lift_from_index(cx: &LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { + let resource = match ty { + InterfaceType::Own(t) => t, + _ => bad_type_info(), + }; + let rep = cx.resource_lift_own(resource, index)?; + // TODO: should debug assert types match here + Ok(Resource::new(rep)) + } +} + +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) => *t, + other => bail!("expected `own` 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: &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: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + let index = u32::load(cx, InterfaceType::U32, bytes)?; + Resource::lift_from_index(cx, ty, index) + } +} + +/// TODO +#[derive(Debug)] +pub struct ResourceAny { + rep: Cell>, + ty: ResourceType, +} + +impl ResourceAny { + /// TODO + pub fn ty(&self) -> ResourceType { + self.ty + } + + fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { + let resource = match ty { + InterfaceType::Own(t) => t, + _ => bad_type_info(), + }; + if cx.resource_type(resource) != self.ty { + bail!("mismatched resource types") + } + let rep = match self.rep.replace(None) { + Some(rep) => rep, + None => bail!("resource already consumed"), + }; + Ok(cx.resource_lower_own(resource, rep)) + } + + fn lift_from_index(cx: &LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { + let resource = match ty { + InterfaceType::Own(t) => t, + _ => bad_type_info(), + }; + let rep = cx.resource_lift_own(resource, index)?; + let ty = cx.resource_type(resource); + Ok(ResourceAny { + rep: Cell::new(Some(rep)), + ty, + }) + } +} + +unsafe impl ComponentType for ResourceAny { + const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; + + type Lower = ::Lower; + + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { + match ty { + InterfaceType::Own(_) => Ok(()), + other => bail!("expected `own` 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: &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: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + let index = u32::load(cx, InterfaceType::U32, bytes)?; + ResourceAny::lift_from_index(cx, ty, index) + } +} diff --git a/crates/wasmtime/src/component/types.rs b/crates/wasmtime/src/component/types.rs index 5467187eb089..37f75aeadbc7 100644 --- a/crates/wasmtime/src/component/types.rs +++ b/crates/wasmtime/src/component/types.rs @@ -12,6 +12,8 @@ use wasmtime_environ::component::{ TypeVariantIndex, }; +pub use crate::component::resources::ResourceType; + #[derive(Clone)] struct Handle { index: T, @@ -482,6 +484,9 @@ impl Type { 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::Own(id) => todo!(), + InterfaceType::Borrow(id) => todo!(), } } diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index e98eb7566bd3..3e5f80cf625c 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -775,6 +775,9 @@ impl Val { value, }) } + + InterfaceType::Own(i) => todo!(), + InterfaceType::Borrow(i) => todo!(), }) } @@ -885,6 +888,9 @@ impl Val { .collect::>()?, }, }), + + InterfaceType::Own(i) => todo!(), + InterfaceType::Borrow(i) => todo!(), }) } 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/spectest.rs b/crates/wast/src/spectest.rs index b699d24aa101..2c44662d9011 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -57,6 +57,8 @@ pub fn link_spectest( #[cfg(feature = "component-model")] pub fn link_component_spectest(linker: &mut component::Linker) -> Result<()> { + use wasmtime::component::Resource; + let engine = linker.engine().clone(); linker .root() @@ -76,5 +78,25 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( "#, )?; i.module("simple-module", &module)?; + + struct Resource1; + struct Resource2; + + // TODO: this is specifying two different destructors for `Resource1`, that + // seems bad. + i.resource::("resource1", |_, _| {})?; + i.resource::("resource2", |_, _| {})?; + i.resource::("resource1-again", |_, _| {})?; + + i.func_wrap("[constructor]resource1", |_, (rep,): (u32,)| { + Ok((Resource::::new(rep),)) + })?; + i.func_wrap( + "[static]resource1.assert", + |_, (resource, rep): (Resource, u32)| { + assert_eq!(resource.rep(), rep); + Ok(()) + }, + )?; Ok(()) } 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..59fda76da7f5 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(()) diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs new file mode 100644 index 000000000000..1c0c3508b62d --- /dev/null +++ b/tests/all/component_model/resources.rs @@ -0,0 +1,382 @@ +use anyhow::Result; +use wasmtime::component::*; +use wasmtime::Store; + +#[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 (own $t))) + (core func $u_drop (canon resource.drop (own $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 (own $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 (own $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 (own $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 (own $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(), + "resource already consumed" + ); + + 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 (own $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(100); + dtor.call(&mut store, (&t,))?; + dtor.post_return(&mut store)?; + + assert_eq!( + dtor.call(&mut store, (&t,)).unwrap_err().to_string(), + "resource already consumed" + ); + + 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..bc635f8e0f6d --- /dev/null +++ b/tests/misc_testsuite/component-model/resources.wast @@ -0,0 +1,663 @@ +;; 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 (own $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 (own $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 (own $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 (own $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 (own $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 (own $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 (own $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 (own $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 (own $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 (own $r))) + (core func $drop2 (canon resource.drop (own $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 (own $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") diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs index 78ceee2b47ad..c547d9f9b776 100644 --- a/winch/codegen/src/codegen/env.rs +++ b/winch/codegen/src/codegen/env.rs @@ -28,9 +28,10 @@ impl<'a, P: PtrSize> FuncEnv<'a, P> { /// Resolves a function [`Callee`] from an index. pub fn callee_from_index(&self, idx: FuncIndex) -> Callee { let types = &self.translation.get_types(); - let ty = types + let id = types .function_at(idx.as_u32()) .unwrap_or_else(|| panic!("function type at index: {}", idx.as_u32())); + let ty = types.type_from_id(id).unwrap().as_func_type().unwrap(); let ty = self.translation.module.convert_func_type(ty); let import = self.translation.module.is_imported_function(idx); From 2c755bd6bf14de9391c45cb4c6b5838a94050b2b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 11:35:12 -0700 Subject: [PATCH 03/47] Start supporting destructors --- crates/cranelift/src/compiler/component.rs | 87 +++++++++++++++++- crates/environ/src/component.rs | 7 +- crates/environ/src/component/dfg.rs | 92 ++++++++----------- crates/environ/src/component/info.rs | 11 ++- .../environ/src/component/translate/inline.rs | 6 ++ .../src/component/vmcomponent_offsets.rs | 21 +++++ crates/runtime/src/component.rs | 33 ++++++- crates/runtime/src/component/resources.rs | 5 +- crates/runtime/src/component/transcode.rs | 14 ++- crates/wasmtime/src/component/instance.rs | 22 ++++- crates/wasmtime/src/component/linker.rs | 15 ++- crates/wasmtime/src/component/matching.rs | 6 +- 12 files changed, 240 insertions(+), 79 deletions(-) diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 2fe9087bb4a7..6b19b7d1b036 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -324,6 +324,7 @@ impl Compiler { 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); @@ -353,8 +354,89 @@ impl Compiler { 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); - builder.func.dfg.inst_results(call); - self.abi_store_results(&mut builder, ty, block0, &[], abi); + let should_run_destructor = builder.func.dfg.inst_results(call)[0]; + + // Synthesize the following: + // + // ... + // brif should_run_destructor, run_destructor_block, return_block + // + // run_destructor_block: + // rep = ushr.i64 rep, 1 + // rep = ireduce.i32 rep + // dtor = load.ptr vmctx+$offset + // brif dtor call_func_ref_block, return_block + // + // call_func_ref_block: + // 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 call_func_ref_block = builder.create_block(); + builder.insert_block_after(call_func_ref_block, run_destructor_block); + let return_block = builder.create_block(); + builder.insert_block_after(return_block, call_func_ref_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); + 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; + let dtor_func_ref = builder.ins().load( + pointer_type, + trusted, + vmctx, + i32::try_from(offsets.resource_destructor(index)).unwrap(), + ); + builder + .ins() + .brif(dtor_func_ref, call_func_ref_block, &[], return_block, &[]); + builder.seal_block(run_destructor_block); + + builder.switch_to_block(call_func_ref_block); + 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); + builder + .ins() + .call_indirect(sig_ref, func_addr, &[callee_vmctx, vmctx, rep]); + builder.ins().jump(return_block, &[]); + builder.seal_block(call_func_ref_block); + + builder.switch_to_block(return_block); + builder.ins().return_(&[]); + builder.seal_block(return_block); builder.finalize(); Ok(Box::new(compiler.finish()?)) @@ -792,6 +874,7 @@ mod host { (@push_return $ptr:ident $params:ident $returns:ident size) => ($returns.push(AbiParam::new($ptr));); (@push_return $ptr:ident $params:ident $returns:ident u32) => ($returns.push(AbiParam::new(ir::types::I32));); + (@push_return $ptr:ident $params:ident $returns:ident u64) => ($returns.push(AbiParam::new(ir::types::I64));); (@push_return $ptr:ident $params:ident $returns:ident size_pair) => ({ $params.push(AbiParam::new($ptr)); $returns.push(AbiParam::new($ptr)); diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index 6947fb18914b..79c2ff710b53 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -78,7 +78,12 @@ macro_rules! foreach_builtin_component_function { $mac! { resource_new32(vmctx: vmctx, resource: u32, rep: u32) -> u32; resource_rep32(vmctx: vmctx, resource: u32, idx: u32) -> u32; - resource_drop(vmctx: vmctx, resource: u32, idx: 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/dfg.rs b/crates/environ/src/component/dfg.rs index 622178148253..42c4b2e782f0 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -117,6 +117,16 @@ pub struct ComponentDfg { /// TODO pub num_resource_tables: usize, + + pub side_effects: Vec, +} + +/// TODO +pub enum SideEffect { + /// TODO + Instance(InstanceId), + /// TODO + Resource(DefinedResourceIndex), } macro_rules! id { @@ -331,21 +341,20 @@ impl ComponentDfg { runtime_always_trap: Default::default(), runtime_lowerings: Default::default(), runtime_transcoders: Default::default(), - // runtime_resources: 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() @@ -371,25 +380,11 @@ impl ComponentDfg { num_resource_rep: linearize.runtime_resource_rep.len() as u32, num_resource_drop: linearize.runtime_resource_drop.len() as u32, - // runtime_resources: { - // let mut list = linearize - // .runtime_resources - // .iter() - // .map(|(id, idx)| (*idx, *id)) - // .collect::>(); - // list.sort_by_key(|(idx, _)| *idx); - // let mut runtime_resources = PrimaryMap::new(); - // for (idx, id) in list { - // let ty = self.resources[id].ty; - // let idx2 = runtime_resources.push(ty); - // assert_eq!(idx, idx2); - // } - // runtime_resources - // }, 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, } } @@ -423,6 +418,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 { @@ -452,6 +458,16 @@ 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, + })); + } + fn export(&mut self, export: &Export) -> info::Export { match export { Export::LiftedFunction { ty, func, options } => { @@ -651,36 +667,6 @@ impl LinearizeDfg<'_> { ) } - // fn resource(&mut self, id: TypeResourceTableIndex) -> TypeResourceTableIndex { - // if self.runtime_resources.insert(id) { - // let dtor = - // self.initializers - // .push(GlobalInitializer::Resource(info::Resource { - // index, - // dtor, - // rep, - // // ty, - // })); - // } - // id - // // let ret = self.intern( - // // id, - // // |me| &mut me.runtime_resources, - // // |me, id| { - // // let info = &me.dfg.resources[id]; - // // ( - // // info.dtor.as_ref().map(|i| me.core_def(i)), - // // info.rep, - // // info.ty, - // // ) - // // }, - // // |index, (dtor, rep, ty)| { - // // }, - // // ); - // // assert_eq!(id, ret); - // // ret - // } - 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 f58409102a38..68f6a86c4ca3 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -157,9 +157,9 @@ pub struct Component { /// TODO pub num_resource_tables: usize, /// TODO + pub num_resources: u32, + /// TODO pub imported_resources: PrimaryMap, - // /// TODO - // pub runtime_resources: PrimaryMap, } impl Component { @@ -170,6 +170,11 @@ impl Component { .checked_sub(self.imported_resources.len() as u32)?; Some(DefinedResourceIndex::from_u32(idx)) } + + /// TODO + pub fn resource_index(&self, idx: DefinedResourceIndex) -> ResourceIndex { + ResourceIndex::from_u32(self.imported_resources.len() as u32 + idx.as_u32()) + } } /// GlobalInitializer instructions to get processed when instantiating a component @@ -553,8 +558,6 @@ pub struct Resource { pub rep: WasmType, /// TODO pub dtor: Option, - // /// TODO - // pub ty: TypeResourceIndex, } /// TODO diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 58a802d582d2..456b845964c8 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -546,6 +546,9 @@ impl<'a> Inliner<'a> { rep: *rep, dtor: dtor.map(|i| frame.funcs[i].clone()), }); + self.result + .side_effects + .push(dfg::SideEffect::Resource(idx)); let idx = self.result.resource_index(idx); types .resources_mut() @@ -610,6 +613,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 diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs index dd4decb72534..bbbe7b114840 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -16,6 +16,7 @@ // 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::*; @@ -67,6 +68,8 @@ pub struct VMComponentOffsets

{ pub num_resource_rep: u32, /// TODO pub num_resource_drop: u32, + /// TODO + pub num_resources: u32, // precalculated offsets of various member fields magic: u32, @@ -84,6 +87,7 @@ pub struct VMComponentOffsets

{ memories: u32, reallocs: u32, post_returns: u32, + resource_destructors: u32, size: u32, } @@ -112,6 +116,7 @@ impl VMComponentOffsets

{ 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, libcalls: 0, store: 0, @@ -127,6 +132,7 @@ impl VMComponentOffsets

{ memories: 0, reallocs: 0, post_returns: 0, + resource_destructors: 0, size: 0, }; @@ -173,6 +179,7 @@ impl VMComponentOffsets

{ 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; @@ -385,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/runtime/src/component.rs b/crates/runtime/src/component.rs index ace40ca41bf1..7dba95916761 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -532,6 +532,19 @@ impl ComponentInstance { }; } + /// See `ComponentInstance::set_resource_destructor` + 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; + } + } + 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.libcalls()) = @@ -600,6 +613,11 @@ 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; + } } } @@ -639,7 +657,11 @@ impl ComponentInstance { } /// TODO - pub fn resource_drop(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result<()> { + pub fn resource_drop( + &mut self, + resource: TypeResourceTableIndex, + idx: u32, + ) -> Result> { self.resource_tables.resource_drop(resource, idx) } } @@ -846,6 +868,15 @@ impl OwnedComponentInstance { } } + /// 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) } + } + /// TODO pub fn imported_resources_mut(&mut self) -> &mut dyn Any { unsafe { &mut *(*self.ptr.as_ptr()).imported_resources } diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index afdd5600178b..f9b64d4303d7 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -35,10 +35,9 @@ impl ResourceTables { self.tables[ty].get(idx) } - pub fn resource_drop(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result<()> { + pub fn resource_drop(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result> { let rep = self.tables[ty].remove(idx)?; - Ok(()) - // TODO: how to run the dtor for `rep`? + Ok(Some(rep)) } pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { diff --git a/crates/runtime/src/component/transcode.rs b/crates/runtime/src/component/transcode.rs index 89dea3c407bd..e86bd77ba03c 100644 --- a/crates/runtime/src/component/transcode.rs +++ b/crates/runtime/src/component/transcode.rs @@ -27,11 +27,11 @@ macro_rules! signature { (@ty ptr_u8) => (*mut u8); (@ty ptr_u16) => (*mut u16); (@ty u32) => (u32); + (@ty u64) => (u64); (@ty vmctx) => (*mut VMComponentContext); (@retptr size_pair) => (*mut usize); - (@retptr size) => (()); - (@retptr u32) => (()); + (@retptr $other:ident) => (()); } /// TODO @@ -156,6 +156,7 @@ mod trampolines { (@convert_ret $ret:ident $retptr:ident) => ($ret); (@convert_ret $ret:ident $retptr:ident size) => ($ret); (@convert_ret $ret:ident $retptr:ident u32) => ($ret); + (@convert_ret $ret:ident $retptr:ident u64) => ($ret); (@convert_ret $ret:ident $retptr:ident size_pair) => ({ let (a, b) = $ret; *$retptr = b; @@ -519,7 +520,12 @@ unsafe fn resource_rep32(vmctx: *mut VMComponentContext, resource: u32, idx: u32 ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_rep32(resource, idx)) } -unsafe fn resource_drop(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result<()> { +unsafe fn resource_drop(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result { let resource = TypeResourceTableIndex::from_u32(resource); - ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_drop(resource, idx)) + 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/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index f446668057a7..ecf4031096fa 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -239,10 +239,10 @@ struct Instantiator<'a> { } #[derive(Clone)] -pub enum RuntimeImport { +pub(crate) enum RuntimeImport { Func(Arc), Module(Module), - Resource(ResourceType, Arc), + Resource(ResourceType, Arc), } pub type ImportedResources = PrimaryMap; @@ -352,7 +352,7 @@ impl<'a> Instantiator<'a> { GlobalInitializer::Transcoder(e) => self.transcoder(e), - GlobalInitializer::Resource(r) => self.resource(r), + 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), @@ -438,8 +438,20 @@ impl<'a> Instantiator<'a> { ) } - fn resource(&mut self, resource: &Resource) { - // TODO + 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); } fn resource_new(&mut self, resource: &ResourceNew) { diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs index dcf4005a1568..6909b6bd55ba 100644 --- a/crates/wasmtime/src/component/linker.rs +++ b/crates/wasmtime/src/component/linker.rs @@ -52,14 +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), + Resource(ResourceType, Arc), } impl Linker { @@ -377,10 +377,15 @@ impl LinkerInstance<'_, T> { pub fn resource( &mut self, name: &str, - dtor: impl Fn(&mut T, u32) + Send + Sync + 'static, + dtor: impl Fn(StoreContextMut<'_, T>, u32) + Send + Sync + 'static, ) -> Result<()> { let name = self.strings.intern(name); - let dtor = Arc::new(move |ptr: *mut u8, val| unsafe { dtor(&mut *ptr.cast(), val) }); + 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)) } diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs index e51488a3bfa4..b42046339ad3 100644 --- a/crates/wasmtime/src/component/matching.rs +++ b/crates/wasmtime/src/component/matching.rs @@ -30,7 +30,11 @@ pub struct InstanceType<'a> { } impl TypeChecker<'_> { - pub fn definition(&mut 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), From b12f079e491de7c87fb769e4685a1eb67f0870f9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 12:24:01 -0700 Subject: [PATCH 04/47] Get some basic drop tests working Also add a test which requires host-defined drop to be called which isn't working. --- crates/wasmtime/src/component/instance.rs | 14 +- crates/wast/src/spectest.rs | 30 +++- .../component-model/resources.wast | 136 ++++++++++++++++++ 3 files changed, 172 insertions(+), 8 deletions(-) diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index ecf4031096fa..3b7b339dfa90 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -276,18 +276,20 @@ impl<'a> Instantiator<'a> { fn run(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> { let env_component = self.component.env_component(); - let imported_resources = self - .data - .state - .imported_resources_mut() - .downcast_mut::() - .unwrap(); for (idx, import) in env_component.imported_resources.iter() { + let imported_resources = self + .data + .state + .imported_resources_mut() + .downcast_mut::() + .unwrap(); let i = imported_resources.push(match &self.imports[*import] { RuntimeImport::Resource(ty, _dtor) => *ty, _ => unreachable!(), }); assert_eq!(i, idx); + // TODO: this should be `Some` + self.data.state.set_resource_destructor(idx, None); } for initializer in env_component.initializers.iter() { match initializer { diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 2c44662d9011..aa469100e6a5 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -57,6 +57,8 @@ 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(); @@ -82,11 +84,27 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( struct Resource1; struct Resource2; + #[derive(Default)] + struct ResourceState { + drops: AtomicU32, + last_drop: AtomicU32, + } + + let state = Arc::new(ResourceState::default()); + // TODO: this is specifying two different destructors for `Resource1`, that // seems bad. - i.resource::("resource1", |_, _| {})?; + i.resource::("resource1", { + let state = state.clone(); + move |_, rep| { + state.drops.fetch_add(1, SeqCst); + state.last_drop.store(rep, SeqCst); + } + })?; i.resource::("resource2", |_, _| {})?; - i.resource::("resource1-again", |_, _| {})?; + i.resource::("resource1-again", |_, _| { + panic!("TODO: shouldn't have to specify dtor twice"); + })?; i.func_wrap("[constructor]resource1", |_, (rep,): (u32,)| { Ok((Resource::::new(rep),)) @@ -98,5 +116,13 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( 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),)) + })?; Ok(()) } diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index bc635f8e0f6d..250482fabfc2 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -661,3 +661,139 @@ (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 (own $r1))) + (core func $drop2 (canon resource.drop (own $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 (own $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)) + ) + + (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)) + )) + )) +) From 1f66eb73d7e2d9ca7a43684faa087bfbe817670b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 13:41:03 -0700 Subject: [PATCH 05/47] Fix rebase issue --- crates/wasmtime/src/component/instance.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 3b7b339dfa90..843823382e3f 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -796,7 +796,8 @@ impl<'a, 'store> ExportInstance<'a, 'store> { } Export::Type(_) | Export::LiftedFunction { .. } - | Export::Module(_) + | Export::ModuleStatic(_) + | Export::ModuleImport(_) | Export::Instance(_) => None, } } From b082ea6366b742c18636d126ba00ad7022da718a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 13:41:12 -0700 Subject: [PATCH 06/47] Fix a failing test --- crates/environ/src/component/translate/inline.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 456b845964c8..4252331726e6 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -404,7 +404,21 @@ impl<'a> Inliner<'a> { // // TODO: update comment Import(name, ty) => { - let arg = &frame.args[name.as_str()]; + let arg = match frame.args.get(name.as_str()) { + Some(arg) => arg, + None => { + match ty { + ComponentEntityType::Type { created, .. } => { + match frame.translation.types_ref().type_from_id(*created) { + Some(wasmparser::types::Type::Resource(_)) => unreachable!(), + _ => {} + } + } + _ => unreachable!(), + } + return Ok(None); + } + }; let mut path = Vec::new(); let (resources, types) = types.resources_mut_and_types(); From e73c1c383f27e339bdbd68fd7d03f38a1a7c00d3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 15:32:30 -0700 Subject: [PATCH 07/47] I am zorthax, destroyer of resources --- crates/cranelift/src/compiler.rs | 8 +- crates/cranelift/src/compiler/component.rs | 4 +- crates/environ/src/compilation.rs | 1 - crates/environ/src/component/types.rs | 14 +- crates/wasmtime/src/compiler.rs | 146 +++++++++++------- crates/wasmtime/src/component/component.rs | 29 +++- crates/wasmtime/src/component/instance.rs | 21 ++- crates/wasmtime/src/component/linker.rs | 6 +- crates/winch/src/compiler.rs | 1 - .../component-model/resources.wast | 7 + 10 files changed, 168 insertions(+), 69 deletions(-) diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 027c7a51f9eb..01a28bda8c53 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,15 @@ impl wasmtime_environ::Compiler for Compiler { caller_vmctx, wasmtime_environ::VMCONTEXT_MAGIC, ); - let offsets = VMOffsets::new(isa.pointer_bytes(), &translation.module); + // 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 6b19b7d1b036..49e95c7dcc61 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -334,6 +334,7 @@ impl Compiler { 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); @@ -428,9 +429,10 @@ impl Compiler { ); 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 ... TODO fill this out builder .ins() - .call_indirect(sig_ref, func_addr, &[callee_vmctx, vmctx, rep]); + .call_indirect(sig_ref, func_addr, &[callee_vmctx, caller_vmctx, rep]); builder.ins().jump(return_block, &[]); builder.seal_block(call_func_ref_block); 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/types.rs b/crates/environ/src/component/types.rs index 2eaa21f6bf96..0c0448e7b397 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; @@ -280,6 +280,18 @@ impl ComponentTypes { InterfaceType::Result(i) => &self[*i].abi, } } + + /// TODO + 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 { diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index 9925b908df14..2b3538c7d8cb 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -24,7 +24,7 @@ use crate::Engine; use anyhow::Result; -use std::collections::{btree_map, BTreeMap, BTreeSet}; +use std::collections::{btree_map, BTreeMap, BTreeSet, HashSet}; use std::{any::Any, collections::HashMap}; use wasmtime_environ::{ Compiler, DefinedFuncIndex, FuncIndex, FunctionBodyData, FunctionLoc, ModuleTranslation, @@ -34,7 +34,7 @@ use wasmtime_environ::{ use wasmtime_jit::{CompiledFunctionInfo, CompiledModuleInfo}; type CompileInput<'a> = - Box Result + Send + 'a>; + Box Result + Send + 'a>; /// A sortable, comparable key for a compilation output. /// @@ -195,34 +195,35 @@ 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 _); + inputs: Vec<(CompileKey, CompileInput<'a>)>, + input_keys: HashSet, + resource_drop_wasm_to_native_trampoline: Option, } impl<'a> CompileInputs<'a> { + fn push_input( + &mut self, + key: CompileKey, + f: impl FnOnce(CompileKey, &Tunables, &dyn Compiler) -> Result + Send + 'a, + ) { + assert!(self.input_keys.insert(key)); + self.inputs.push((key, 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. @@ -238,20 +239,17 @@ 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| { + let key = CompileKey::always_trap(always_trap.index); + ret.push_input(key, move |key, _tunables, compiler| { Ok(CompileOutput { - key: CompileKey::always_trap(always_trap.index), + key, symbol: always_trap.symbol_name(), function: compiler .component_compiler() @@ -262,9 +260,10 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::Transcoder(transcoder) => { - push_input(&mut inputs, move |_tunables, compiler| { + let key = CompileKey::transcoder(transcoder.index); + ret.push_input(key, move |key, _tunables, compiler| { Ok(CompileOutput { - key: CompileKey::transcoder(transcoder.index), + key, symbol: transcoder.symbol_name(), function: compiler .component_compiler() @@ -275,9 +274,10 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::LowerImport(lower_import) => { - push_input(&mut inputs, move |_tunables, compiler| { + let key = CompileKey::lowering(lower_import.index); + ret.push_input(key, move |key, _tunables, compiler| { Ok(CompileOutput { - key: CompileKey::lowering(lower_import.index), + key, symbol: lower_import.symbol_name(), function: compiler .component_compiler() @@ -289,9 +289,10 @@ impl<'a> CompileInputs<'a> { } wasmtime_environ::component::GlobalInitializer::ResourceNew(r) => { - push_input(&mut inputs, move |_tunables, compiler| { + let key = CompileKey::resource_new(r.index); + ret.push_input(key, move |key, _tunables, compiler| { Ok(CompileOutput { - key: CompileKey::resource_new(r.index), + key, symbol: r.symbol_name(), function: compiler .component_compiler() @@ -302,9 +303,10 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::ResourceRep(r) => { - push_input(&mut inputs, move |_tunables, compiler| { + let key = CompileKey::resource_rep(r.index); + ret.push_input(key, move |key, _tunables, compiler| { Ok(CompileOutput { - key: CompileKey::resource_rep(r.index), + key, symbol: r.symbol_name(), function: compiler .component_compiler() @@ -315,9 +317,10 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::ResourceDrop(r) => { - push_input(&mut inputs, move |_tunables, compiler| { + let key = CompileKey::resource_drop(r.index); + ret.push_input(key, move |key, _tunables, compiler| { Ok(CompileOutput { - key: CompileKey::resource_drop(r.index), + key, symbol: r.symbol_name(), function: compiler .component_compiler() @@ -338,10 +341,29 @@ impl<'a> CompileInputs<'a> { } } - CompileInputs { inputs } + if component.num_resources > 0 { + if let Some(sig) = types.find_resource_drop_signature() { + let key = CompileKey::wasm_to_native_trampoline(sig); + ret.resource_drop_wasm_to_native_trampoline = Some(key); + if !ret.input_keys.contains(&key) { + ret.push_input(key, move |key, _tunables, compiler| { + let trampoline = compiler.compile_wasm_to_native_trampoline(&types[sig])?; + Ok(CompileOutput { + key, + 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 = ( @@ -350,13 +372,13 @@ 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| { + let key = CompileKey::wasm_function(module, def_func_index); + self.push_input(key, move |key, tunables, compiler| { let func_index = translation.module.func_index(def_func_index); let (info, function) = compiler.compile_function( translation, @@ -366,7 +388,7 @@ impl<'a> CompileInputs<'a> { types, )?; Ok(CompileOutput { - key: CompileKey::wasm_function(module, def_func_index), + key, symbol: format!( "wasm[{}]::function[{}]", module.as_u32(), @@ -379,7 +401,8 @@ 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| { + let key = CompileKey::array_to_wasm_trampoline(module, def_func_index); + self.push_input(key, move |key, _tunables, compiler| { let func_index = translation.module.func_index(def_func_index); let trampoline = compiler.compile_array_to_wasm_trampoline( translation, @@ -387,7 +410,7 @@ impl<'a> CompileInputs<'a> { def_func_index, )?; Ok(CompileOutput { - key: CompileKey::array_to_wasm_trampoline(module, def_func_index), + key, symbol: format!( "wasm[{}]::array_to_wasm_trampoline[{}]", module.as_u32(), @@ -398,7 +421,8 @@ impl<'a> CompileInputs<'a> { }) }); - push_input(inputs, move |_tunables, compiler| { + let key = CompileKey::native_to_wasm_trampoline(module, def_func_index); + self.push_input(key, move |key, _tunables, compiler| { let func_index = translation.module.func_index(def_func_index); let trampoline = compiler.compile_native_to_wasm_trampoline( translation, @@ -406,7 +430,7 @@ impl<'a> CompileInputs<'a> { def_func_index, )?; Ok(CompileOutput { - key: CompileKey::native_to_wasm_trampoline(module, def_func_index), + key, symbol: format!( "wasm[{}]::native_to_wasm_trampoline[{}]", module.as_u32(), @@ -420,17 +444,17 @@ 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 { + let key = CompileKey::wasm_to_native_trampoline(signature); + self.push_input(key, move |key, _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), + key, symbol: format!( "signatures[{}]::wasm_to_native_trampoline", signature.as_u32() @@ -449,7 +473,8 @@ impl<'a> CompileInputs<'a> { let compiler = engine.compiler(); // Compile each individual input in parallel. - let raw_outputs = engine.run_maybe_parallel(self.inputs, |f| f(tunables, compiler))?; + let raw_outputs = + engine.run_maybe_parallel(self.inputs, |(key, f)| f(key, tunables, compiler))?; // Bucket the outputs by kind. let mut outputs: BTreeMap> = BTreeMap::new(); @@ -471,7 +496,10 @@ impl<'a> CompileInputs<'a> { .values() .all(|funcs| is_sorted_by_key(funcs, |x| x.key))); - Ok(UnlinkedCompileOutputs { outputs }) + Ok(UnlinkedCompileOutputs { + outputs, + resource_drop_wasm_to_native_trampoline: self.resource_drop_wasm_to_native_trampoline, + }) } } @@ -479,6 +507,8 @@ impl<'a> CompileInputs<'a> { pub struct UnlinkedCompileOutputs { // A map from kind to `CompileOutput`. outputs: BTreeMap>, + + resource_drop_wasm_to_native_trampoline: Option, } impl UnlinkedCompileOutputs { @@ -533,6 +563,8 @@ impl UnlinkedCompileOutputs { .or_default() .insert(x.key, index); } + indices.resource_drop_wasm_to_native_trampoline = + self.resource_drop_wasm_to_native_trampoline; (compiled_funcs, indices) } } @@ -548,6 +580,9 @@ pub struct FunctionIndices { // The index of each compiled function, bucketed by compile key kind. indices: BTreeMap>>, + + // TODO + resource_drop_wasm_to_native_trampoline: Option, } impl FunctionIndices { @@ -765,6 +800,11 @@ impl FunctionIndices { .into_iter() .map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1)) .collect(); + artifacts.resource_drop_wasm_to_native_trampoline = + self.resource_drop_wasm_to_native_trampoline.map(|i| { + let func = wasm_to_native_trampolines[&i].unwrap_function(); + symbol_ids_and_locs[func].1 + }); } debug_assert!( @@ -811,6 +851,8 @@ pub struct Artifacts { wasmtime_environ::component::RuntimeResourceDropIndex, wasmtime_environ::component::AllCallFunc, >, + #[cfg(feature = "component-model")] + pub resource_drop_wasm_to_native_trampoline: Option, } impl Artifacts { diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index eeee21629fae..4ca6db9d4196 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -17,7 +17,8 @@ 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. @@ -81,6 +82,8 @@ struct CompiledComponentInfo { resource_rep: PrimaryMap>, /// TODO resource_drop: PrimaryMap>, + + resource_drop_wasm_to_native_trampoline: Option, } pub(crate) struct AllCallFuncPointers { @@ -236,6 +239,8 @@ impl Component { 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, @@ -383,6 +388,28 @@ impl Component { pub(crate) fn runtime_info(&self) -> Arc { self.inner.clone() } + + 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()); + + // TODO + let wasm_call = self + .inner + .info + .resource_drop_wasm_to_native_trampoline + .as_ref() + .map(|i| { + let ptr = self.func(i); + unsafe { + mem::transmute::, NonNull>(ptr) + } + }); + VMFuncRef { + wasm_call, + ..*dtor.func_ref() + } + } } impl ComponentRuntimeInfo for ComponentInner { diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 843823382e3f..0ed00adf796b 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -15,7 +15,8 @@ use wasmtime_environ::component::*; use wasmtime_environ::{EntityIndex, EntityType, Global, PrimaryMap, SignatureIndex, WasmType}; use wasmtime_runtime::component::{ComponentInstance, OwnedComponentInstance}; use wasmtime_runtime::{ - VMArrayCallFunction, VMNativeCallFunction, VMSharedSignatureIndex, VMWasmCallFunction, + VMArrayCallFunction, VMFuncRef, VMNativeCallFunction, VMSharedSignatureIndex, + VMWasmCallFunction, }; /// An instantiated component. @@ -238,11 +239,14 @@ struct Instantiator<'a> { imports: &'a PrimaryMap, } -#[derive(Clone)] pub(crate) enum RuntimeImport { Func(Arc), Module(Module), - Resource(ResourceType, Arc), + Resource { + ty: ResourceType, + dtor: Arc, + dtor_funcref: VMFuncRef, + }, } pub type ImportedResources = PrimaryMap; @@ -283,13 +287,16 @@ impl<'a> Instantiator<'a> { .imported_resources_mut() .downcast_mut::() .unwrap(); - let i = imported_resources.push(match &self.imports[*import] { - RuntimeImport::Resource(ty, _dtor) => *ty, + let (ty, func_ref) = match &self.imports[*import] { + RuntimeImport::Resource { + ty, dtor_funcref, .. + } => (*ty, NonNull::from(dtor_funcref)), _ => unreachable!(), - }); + }; + let i = imported_resources.push(ty); assert_eq!(i, idx); // TODO: this should be `Some` - self.data.state.set_resource_destructor(idx, None); + self.data.state.set_resource_destructor(idx, Some(func_ref)); } for initializer in env_component.initializers.iter() { match initializer { diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs index 6909b6bd55ba..15fb3e6a6d84 100644 --- a/crates/wasmtime/src/component/linker.rs +++ b/crates/wasmtime/src/component/linker.rs @@ -175,7 +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(t.clone(), dtor.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. 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/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index 250482fabfc2..c89a44d0c116 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -784,6 +784,13 @@ ;; 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) From 71dbce7bc37b6ba217b2def466f2d135cb76ff8e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 15:44:35 -0700 Subject: [PATCH 08/47] Remove a branch in compiled code No need to check for a null funcref when we already know ahead of time if it's ever going to be null or not. --- crates/cranelift/src/compiler/component.rs | 170 +++++++++++---------- 1 file changed, 90 insertions(+), 80 deletions(-) diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 49e95c7dcc61..1362db0b47f1 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -357,88 +357,98 @@ impl Compiler { let call = builder.ins().call_indirect(host_sig, host_fn, &host_args); let should_run_destructor = builder.func.dfg.inst_results(call)[0]; - // Synthesize the following: - // - // ... - // brif should_run_destructor, run_destructor_block, return_block - // - // run_destructor_block: - // rep = ushr.i64 rep, 1 - // rep = ireduce.i32 rep - // dtor = load.ptr vmctx+$offset - // brif dtor call_func_ref_block, return_block - // - // call_func_ref_block: - // 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 call_func_ref_block = builder.create_block(); - builder.insert_block_after(call_func_ref_block, run_destructor_block); - let return_block = builder.create_block(); - builder.insert_block_after(return_block, call_func_ref_block); - - builder.ins().brif( - should_run_destructor, - run_destructor_block, - &[], - return_block, - &[], - ); - - let trusted = ir::MemFlags::trusted().with_readonly(); + let resource_ty = types[resource.resource].ty; + let has_destructor = match component.defined_resource_index(resource_ty) { + Some(idx) => component.initializers.iter().any(|i| match i { + GlobalInitializer::Resource(r) => r.index == idx && r.dtor.is_some(), + _ => false, + }), + None => true, + }; + if has_destructor { + // Synthesize the following: + // + // ... + // brif should_run_destructor, run_destructor_block, return_block + // + // run_destructor_block: + // 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, + &[], + ); - builder.switch_to_block(run_destructor_block); - 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; - let dtor_func_ref = builder.ins().load( - pointer_type, - trusted, - vmctx, - i32::try_from(offsets.resource_destructor(index)).unwrap(), - ); - builder - .ins() - .brif(dtor_func_ref, call_func_ref_block, &[], return_block, &[]); - builder.seal_block(run_destructor_block); + let trusted = ir::MemFlags::trusted().with_readonly(); - builder.switch_to_block(call_func_ref_block); - 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 ... TODO fill this out - builder - .ins() - .call_indirect(sig_ref, func_addr, &[callee_vmctx, caller_vmctx, rep]); - builder.ins().jump(return_block, &[]); - builder.seal_block(call_func_ref_block); - - builder.switch_to_block(return_block); - builder.ins().return_(&[]); - builder.seal_block(return_block); + builder.switch_to_block(run_destructor_block); + 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(), + ); + 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); + } else { + // If this resource has no destructor, then after the table is + // updated there's nothing to do, so we can return. + builder.ins().return_(&[]); + } builder.finalize(); Ok(Box::new(compiler.finish()?)) From fb46bf793366b37659e663c75c3e4e603d09aed4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jul 2023 21:43:34 -0700 Subject: [PATCH 09/47] Fix the test suite --- Cargo.lock | 188 +++++++++++++++++-------------- Cargo.toml | 26 +++-- crates/environ/examples/factc.rs | 6 +- crates/wit-bindgen/src/lib.rs | 2 + crates/wit-bindgen/src/rust.rs | 6 + crates/wit-bindgen/src/types.rs | 1 + winch/filetests/src/lib.rs | 3 +- winch/src/compile.rs | 3 +- 8 files changed, 135 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e049a1759558..63445e8d7f03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -686,7 +686,7 @@ dependencies = [ "target-lexicon", "thiserror", "toml", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wat", ] @@ -863,7 +863,7 @@ dependencies = [ "serde", "smallvec", "target-lexicon", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime-types", "wat", ] @@ -2517,9 +2517,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -2862,22 +2862,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" dependencies = [ "proc-macro2", "quote", - "syn 1.0.92", + "syn 2.0.23", ] [[package]] @@ -3004,6 +3004,15 @@ dependencies = [ "id-arena", ] +[[package]] +name = "spdx" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2971cb691ca629f46174f73b1f95356c5617f89b4167f04107167c3dccb8dd89" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.5.2" @@ -3074,9 +3083,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ "proc-macro2", "quote", @@ -3185,7 +3194,7 @@ dependencies = [ "wasmtime", "wasmtime-wasi", "wasmtime-wasi-http", - "wit-component", + "wit-component 0.12.0", ] [[package]] @@ -3709,17 +3718,8 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.29.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +version = "0.29.1" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "leb128", ] @@ -3733,48 +3733,48 @@ dependencies = [ "anyhow", "indexmap 1.9.1", "serde", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.29.1", "wasmparser 0.107.0", ] [[package]] name = "wasm-metadata" -version = "0.8.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +version = "0.9.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "anyhow", "indexmap 2.0.0", "serde", - "wasm-encoder 0.29.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", - "wasmparser 0.107.0", + "serde_json", + "spdx", + "wasm-encoder 0.29.1", + "wasmparser 0.108.0", ] [[package]] name = "wasm-mutate" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354beb7e2ccaf66dd263328486240899755ae0df3ac4b76305dace67fbd2169" +version = "0.2.28" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "egg", "log", "rand 0.8.5", "thiserror", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.107.0", + "wasm-encoder 0.29.1", + "wasmparser 0.108.0", ] [[package]] name = "wasm-smith" -version = "0.12.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027ec1c470cd5d56c43b8e02040250b136ddb5975dd76a1c16915137f5f17e76" +version = "0.12.11" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "arbitrary", "flagset", - "indexmap 1.9.1", + "indexmap 2.0.0", "leb128", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.107.0", + "wasm-encoder 0.29.1", + "wasmparser 0.108.0", ] [[package]] @@ -3827,7 +3827,17 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.107.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" +dependencies = [ + "indexmap 1.9.1", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.108.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "indexmap 2.0.0", "semver", @@ -3844,11 +3854,11 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.59" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +version = "0.2.60" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "anyhow", - "wasmparser 0.107.0", + "wasmparser 0.108.0", ] [[package]] @@ -3875,7 +3885,7 @@ dependencies = [ "target-lexicon", "tempfile", "wasi-cap-std-sync", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", @@ -3990,8 +4000,8 @@ dependencies = [ "test-programs", "tokio", "walkdir", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.107.0", + "wasm-encoder 0.29.1", + "wasmparser 0.108.0", "wasmtime", "wasmtime-cache", "wasmtime-cli-flags", @@ -4006,7 +4016,7 @@ dependencies = [ "wasmtime-wasi-nn", "wasmtime-wasi-threads", "wasmtime-wast", - "wast 60.0.0", + "wast 60.0.1", "wat", "windows-sys 0.48.0", ] @@ -4036,7 +4046,7 @@ dependencies = [ "wasmtime", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-parser 0.9.0", ] [[package]] @@ -4059,7 +4069,7 @@ dependencies = [ "object", "target-lexicon", "thiserror", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -4094,8 +4104,8 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.107.0", + "wasm-encoder 0.29.1", + "wasmparser 0.108.0", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -4110,7 +4120,7 @@ dependencies = [ "component-fuzz-util", "env_logger 0.10.0", "libfuzzer-sys", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmprinter", "wasmtime-environ", "wat", @@ -4164,7 +4174,7 @@ dependencies = [ "rand 0.8.5", "smallvec", "target-lexicon", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime", "wasmtime-fuzzing", ] @@ -4184,12 +4194,12 @@ dependencies = [ "target-lexicon", "tempfile", "v8", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.29.1", "wasm-mutate", "wasm-smith", "wasm-spec-interpreter", "wasmi", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmprinter", "wasmtime", "wasmtime-wast", @@ -4271,7 +4281,7 @@ dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser 0.107.0", + "wasmparser 0.108.0", ] [[package]] @@ -4358,7 +4368,7 @@ dependencies = [ "anyhow", "log", "wasmtime", - "wast 60.0.0", + "wast 60.0.1", ] [[package]] @@ -4370,7 +4380,7 @@ dependencies = [ "gimli", "object", "target-lexicon", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime-cranelift-shared", "wasmtime-environ", "winch-codegen", @@ -4382,7 +4392,7 @@ version = "12.0.0" dependencies = [ "anyhow", "heck", - "wit-parser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-parser 0.9.0", ] [[package]] @@ -4396,23 +4406,21 @@ dependencies = [ [[package]] name = "wast" -version = "60.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd06cc744b536e30387e72a48fdd492105b9c938bb4f415c39c616a7a0a697ad" +version = "60.0.1" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.29.1", ] [[package]] name = "wat" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abe520f0ab205366e9ac7d3e6b2fc71de44e32a2b58f2ec871b6b575bdcea3b" +version = "1.0.67" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ - "wast 60.0.0", + "wast 60.0.1", ] [[package]] @@ -4540,7 +4548,7 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime-environ", ] @@ -4584,7 +4592,7 @@ dependencies = [ "similar", "target-lexicon", "toml", - "wasmparser 0.107.0", + "wasmparser 0.108.0", "wasmtime-environ", "wat", "winch-codegen", @@ -4752,8 +4760,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34a19aa69c4f33cb5ac10e55880a899f4d52ec85d4cde4d593b575e7a97e2b08" dependencies = [ "anyhow", - "wit-component", - "wit-parser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component 0.11.0", + "wit-parser 0.8.0", ] [[package]] @@ -4763,10 +4771,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a50274c0cf2f8e33fc967825cef0114cdfe222d474c1d78aa77a6a801abaadf" dependencies = [ "heck", - "wasm-metadata 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-metadata 0.8.0", "wit-bindgen-core", "wit-bindgen-rust-lib", - "wit-component", + "wit-component 0.11.0", ] [[package]] @@ -4787,25 +4795,41 @@ checksum = "78cce32dd08007af45dbaa00e225eb73d05524096f93933d7ecba852d50d8af3" dependencies = [ "anyhow", "proc-macro2", - "syn 2.0.16", + "syn 2.0.23", "wit-bindgen-core", "wit-bindgen-rust", - "wit-component", + "wit-component 0.11.0", ] [[package]] name = "wit-component" version = "0.11.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" dependencies = [ "anyhow", "bitflags 1.3.2", - "indexmap 2.0.0", + "indexmap 1.9.1", "log", - "wasm-encoder 0.29.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", - "wasm-metadata 0.8.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", + "wasm-encoder 0.29.1", + "wasm-metadata 0.8.0", "wasmparser 0.107.0", - "wit-parser 0.8.0 (git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources)", + "wit-parser 0.8.0", +] + +[[package]] +name = "wit-component" +version = "0.12.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "indexmap 2.0.0", + "log", + "wasm-encoder 0.29.1", + "wasm-metadata 0.9.0", + "wasmparser 0.108.0", + "wit-parser 0.9.0", ] [[package]] @@ -4826,8 +4850,8 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.8.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#27f239aa0cccc2cb3ae2a80bf53ad3f3fb3dfaef" +version = "0.9.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" dependencies = [ "anyhow", "id-arena", diff --git a/Cargo.toml b/Cargo.toml index b9d0aaf66bc5..9cf636de5ef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,15 +199,15 @@ rustix = "0.37.13" wit-bindgen = { version = "0.7.0", default-features = false } # wasm-tools family: -wasmparser = "0.107.0" -wat = "1.0.66" -wast = "60.0.0" +wasmparser = "0.108.0" +wat = "1.0.67" +wast = "60.0.1" wasmprinter = "0.2.59" -wasm-encoder = "0.29.0" -wasm-smith = "0.12.10" -wasm-mutate = "0.2.27" -wit-parser = "0.8.0" -wit-component = "0.11.0" +wasm-encoder = "0.29.1" +wasm-smith = "0.12.11" +wasm-mutate = "0.2.28" +wit-parser = "0.9.0" +wit-component = "0.12.0" # Non-Bytecode Alliance maintained dependencies: # -------------------------- @@ -313,9 +313,13 @@ debug-assertions = false overflow-checks = false [patch.crates-io] +wat = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wast = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } wasmparser = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } wasmprinter = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wasm-mutate = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wasm-smith = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wasm-encoder = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } +wasm-metadata = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } wit-component = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -# wasmparser = { path = '../wasm-tools/crates/wasmparser' } -# wasmprinter = { path = '../wasm-tools/crates/wasmprinter' } -# wit-component = { path = '../wasm-tools/crates/wit-component' } +wit-parser = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs index 473168d9a66b..e0f144a604bb 100644 --- a/crates/environ/examples/factc.rs +++ b/crates/environ/examples/factc.rs @@ -133,11 +133,7 @@ impl Factc { Some(ty) => ty, None => break, }; - let wasm_ty = wasm_types.type_from_id(ty).unwrap(); - let ty = match wasm_ty.as_component_func_type() { - Some(ty) => types.convert_component_func_type(wasm_types, ty)?, - None => continue, - }; + let ty = types.convert_component_func_type(wasm_types, ty)?; adapters.push(Adapter { lift_ty: ty, lower_ty: ty, diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index f4812254ea92..48a250c3fcb2 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -681,6 +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!("generate for handle"), + TypeDefKind::Resource => todo!("generate for resource"), TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-bindgen/src/rust.rs b/crates/wit-bindgen/src/rust.rs index 4bbc12ff54e7..27b0c6fed429 100644 --- a/crates/wit-bindgen/src/rust.rs +++ b/crates/wit-bindgen/src/rust.rs @@ -123,6 +123,8 @@ pub trait RustGenerator<'a> { } TypeDefKind::Type(Type::String) => true, TypeDefKind::Type(_) => false, + TypeDefKind::Resource => todo!(), + TypeDefKind::Handle(_) => todo!(), TypeDefKind::Unknown => unreachable!(), } } @@ -184,6 +186,8 @@ pub trait RustGenerator<'a> { } TypeDefKind::Type(t) => self.print_ty(t, mode), + TypeDefKind::Handle(_) => todo!(), + TypeDefKind::Resource => todo!(), TypeDefKind::Unknown => unreachable!(), } } @@ -292,6 +296,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::Unknown => unreachable!(), }, } diff --git a/crates/wit-bindgen/src/types.rs b/crates/wit-bindgen/src/types.rs index 223bfcc91ff9..b315023fa822 100644 --- a/crates/wit-bindgen/src/types.rs +++ b/crates/wit-bindgen/src/types.rs @@ -158,6 +158,7 @@ impl Types { info = self.optional_type_info(resolve, stream.element.as_ref()); info |= self.optional_type_info(resolve, stream.end.as_ref()); } + TypeDefKind::Handle(_) | TypeDefKind::Resource => {} TypeDefKind::Unknown => unreachable!(), } self.type_info.insert(ty, info); diff --git a/winch/filetests/src/lib.rs b/winch/filetests/src/lib.rs index 24b32ef8c23d..e30692394664 100644 --- a/winch/filetests/src/lib.rs +++ b/winch/filetests/src/lib.rs @@ -154,9 +154,10 @@ mod test { let types = &translation.get_types(); let index = module.func_index(f.0); - let sig = types + let id = types .function_at(index.as_u32()) .expect(&format!("function type at index {:?}", index.as_u32())); + let sig = types.type_from_id(id).unwrap().as_func_type().unwrap(); let sig = translation.module.convert_func_type(&sig); let FunctionBodyData { body, validator } = f.1; diff --git a/winch/src/compile.rs b/winch/src/compile.rs index 154d537913e4..d09d7ed4708a 100644 --- a/winch/src/compile.rs +++ b/winch/src/compile.rs @@ -53,9 +53,10 @@ fn compile( ) -> Result<()> { let index = translation.module.func_index(f.0); let types = &translation.get_types(); - let sig = types + let id = types .function_at(index.as_u32()) .expect(&format!("function type at index {:?}", index.as_u32())); + let sig = types.type_from_id(id).unwrap().as_func_type().unwrap(); let sig = translation.module.convert_func_type(sig); let FunctionBodyData { body, validator } = f.1; let mut validator = validator.into_validator(Default::default()); From 505b46beec43d960b7c7f0ea9537cec9ddb69f50 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 6 Jul 2023 06:53:11 -0700 Subject: [PATCH 10/47] Add embedder API to destroy resources --- crates/runtime/src/component.rs | 22 ++++- crates/wasmtime/src/component/func/options.rs | 6 +- crates/wasmtime/src/component/resources.rs | 50 ++++++++++- tests/all/component_model/resources.rs | 89 +++++++++++++++++++ 4 files changed, 161 insertions(+), 6 deletions(-) diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index 7dba95916761..d2c92caeaa00 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -532,7 +532,7 @@ impl ComponentInstance { }; } - /// See `ComponentInstance::set_resource_destructor` + /// TODO pub fn set_resource_destructor( &mut self, idx: ResourceIndex, @@ -545,6 +545,15 @@ impl ComponentInstance { } } + /// TODO + pub fn resource_destructor(&mut 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.libcalls()) = @@ -647,8 +656,15 @@ impl ComponentInstance { } /// TODO - pub fn resource_lift_own(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { - self.resource_tables.resource_lift_own(ty, idx) + pub fn resource_lift_own( + &mut self, + ty: TypeResourceTableIndex, + idx: u32, + ) -> Result<(u32, Option>)> { + let rep = self.resource_tables.resource_lift_own(ty, idx)?; + let resource = self.component_types()[ty].ty; + let dtor = self.resource_destructor(resource); + Ok((rep, dtor)) } /// TODO diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index b6878ad6b14c..7efb096f910c 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -329,7 +329,11 @@ impl<'a> LiftContext<'a> { } /// TODO - pub fn resource_lift_own(&self, ty: TypeResourceTableIndex, idx: u32) -> Result { + pub fn resource_lift_own( + &self, + ty: TypeResourceTableIndex, + idx: u32, + ) -> Result<(u32, Option>)> { // TODO: document unsafe unsafe { (*self.instance).resource_lift_own(ty, idx) } } diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index 8eb68739636a..282b68b24302 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -2,6 +2,7 @@ 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; +use crate::{AsContextMut, StoreContextMut}; use anyhow::{bail, Result}; use std::any::TypeId; use std::cell::Cell; @@ -9,6 +10,7 @@ use std::marker; use std::mem::MaybeUninit; use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType}; use wasmtime_runtime::component::ComponentInstance; +use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw}; /// TODO #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -91,7 +93,7 @@ impl Resource { InterfaceType::Own(t) => t, _ => bad_type_info(), }; - let rep = cx.resource_lift_own(resource, index)?; + let (rep, _dtor) = cx.resource_lift_own(resource, index)?; // TODO: should debug assert types match here Ok(Resource::new(rep)) } @@ -153,8 +155,10 @@ unsafe impl Lift for Resource { /// TODO #[derive(Debug)] pub struct ResourceAny { + store: StoreId, rep: Cell>, ty: ResourceType, + dtor: Option>, } impl ResourceAny { @@ -163,6 +167,46 @@ impl ResourceAny { self.ty } + /// TODO + 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()) + } + + /// TODO + 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<()> { + assert_eq!(store.0.id(), self.store); + let rep = match self.rep.replace(None) { + Some(rep) => rep, + None => bail!("resource already consumed"), + }; + let dtor = match self.dtor { + Some(dtor) => dtor.as_non_null(), + None => return Ok(()), + }; + let mut args = [ValRaw::u32(rep)]; + // TODO: unsafe call + 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 { let resource = match ty { InterfaceType::Own(t) => t, @@ -183,11 +227,13 @@ impl ResourceAny { InterfaceType::Own(t) => t, _ => bad_type_info(), }; - let rep = cx.resource_lift_own(resource, index)?; + let (rep, dtor) = cx.resource_lift_own(resource, index)?; let ty = cx.resource_type(resource); Ok(ResourceAny { + store: cx.store.id(), rep: Cell::new(Some(rep)), ty, + dtor: dtor.map(SendSyncPtr::new), }) } } diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 1c0c3508b62d..11cbdf54cbc2 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -380,3 +380,92 @@ fn drop_host_twice() -> Result<()> { 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(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(()) +} From 33f7c6250c71a73e0c5b42b24e5c42609c57ce85 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 6 Jul 2023 06:57:01 -0700 Subject: [PATCH 11/47] Add TODO for factc --- crates/environ/src/fact/trampoline.rs | 72 +++++++++++++-------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index a6e278bcfc06..40f056e66b07 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}; @@ -590,10 +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), - InterfaceType::Own(t) => todo!(), - InterfaceType::Borrow(t) => todo!(), + InterfaceType::Own(t) => self.translate_own(*t, src, dst_ty, dst), + InterfaceType::Borrow(t) => self.translate_borrow(*t, src, dst_ty, dst), } } @@ -2454,37 +2452,37 @@ impl Compiler<'_, '_> { } } - // fn translate_own( - // &mut self, - // src_ty: ResourceId, - // src: &Source<'_>, - // dst_ty: &InterfaceType, - // dst: &Destination, - // ) { - // let dst_ty = match dst_ty { - // InterfaceType::Own(t) => t, - // _ => panic!("expected an `Own`"), - // }; - - // drop((src_ty, src, dst_ty, dst)); - // todo!(); - // } - - // fn translate_borrow( - // &mut self, - // src_ty: ResourceId, - // src: &Source<'_>, - // dst_ty: &InterfaceType, - // dst: &Destination, - // ) { - // let dst_ty = match dst_ty { - // InterfaceType::Borrow(t) => t, - // _ => panic!("expected an `Borrow`"), - // }; - - // drop((src_ty, src, dst_ty, dst)); - // todo!(); - // } + 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`"), + }; + + drop((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`"), + }; + + drop((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())); From 933bea83563a8d0a0926744980294c77a7591dae Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 6 Jul 2023 06:59:32 -0700 Subject: [PATCH 12/47] Fix a warning and leave a comment --- crates/wasmtime/src/component/instance.rs | 16 +++++++++++++++- crates/wasmtime/src/component/linker.rs | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 0ed00adf796b..0ee795ef87f2 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -244,7 +244,21 @@ pub(crate) enum RuntimeImport { Module(Module), Resource { ty: ResourceType, - dtor: Arc, + + // 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, }, } diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs index 15fb3e6a6d84..a6e9689b641e 100644 --- a/crates/wasmtime/src/component/linker.rs +++ b/crates/wasmtime/src/component/linker.rs @@ -177,7 +177,7 @@ impl Linker { Definition::Func(f) => RuntimeImport::Func(f.clone()), Definition::Resource(t, dtor) => RuntimeImport::Resource { ty: t.clone(), - dtor: dtor.clone(), + _dtor: dtor.clone(), dtor_funcref: component.resource_drop_func_ref(dtor), }, From 4ebed1f8d7b660648f82db6714120ec2fdc70e82 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 6 Jul 2023 08:28:29 -0700 Subject: [PATCH 13/47] Integrate resources into `Type` Plumb around dynamic information about resource types. --- crates/runtime/src/component.rs | 18 +- crates/wasmtime/src/component/func.rs | 12 +- crates/wasmtime/src/component/func/host.rs | 9 +- crates/wasmtime/src/component/func/options.rs | 16 +- crates/wasmtime/src/component/instance.rs | 21 ++- crates/wasmtime/src/component/matching.rs | 44 +++-- crates/wasmtime/src/component/types.rs | 165 ++++++++++-------- crates/wasmtime/src/component/values.rs | 34 ++-- tests/all/component_model/resources.rs | 46 +++++ 9 files changed, 233 insertions(+), 132 deletions(-) diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index d2c92caeaa00..b7245de44b22 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -53,7 +53,7 @@ pub struct ComponentInstance { /// TODO: /// TODO: Any is bad - imported_resources: Box, + resource_types: Arc, /// A zero-sized field which represents the end of the struct for the actual /// `VMComponentContext` to be allocated behind. @@ -171,7 +171,7 @@ impl ComponentInstance { alloc_size: usize, offsets: VMComponentOffsets, runtime_info: Arc, - imported_resources: Box, + resource_types: Arc, store: *mut dyn Store, ) { assert!(alloc_size >= Self::alloc_layout(&offsets).size()); @@ -191,7 +191,7 @@ impl ComponentInstance { ), resource_tables: ResourceTables::new(runtime_info.component().num_resource_tables), runtime_info, - imported_resources, + resource_types, vmctx: VMComponentContext { _marker: marker::PhantomPinned, }, @@ -641,8 +641,8 @@ impl ComponentInstance { } /// TODO - pub fn imported_resources(&self) -> &dyn Any { - &*self.imported_resources + pub fn resource_types(&self) -> &Arc { + &self.resource_types } /// TODO @@ -708,7 +708,7 @@ impl OwnedComponentInstance { /// heap with `malloc` and configures it for the `component` specified. pub fn new( runtime_info: Arc, - imported_resources: Box, + resource_types: Arc, store: *mut dyn Store, ) -> OwnedComponentInstance { let component = runtime_info.component(); @@ -731,7 +731,7 @@ impl OwnedComponentInstance { layout.size(), offsets, runtime_info, - imported_resources, + resource_types, store, ); @@ -894,8 +894,8 @@ impl OwnedComponentInstance { } /// TODO - pub fn imported_resources_mut(&mut self) -> &mut dyn Any { - unsafe { &mut *(*self.ptr.as_ptr()).imported_resources } + pub fn resource_types_mut(&mut self) -> &mut Arc { + unsafe { &mut (*self.ptr.as_ptr()).resource_types } } } diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 718a9b46db72..3074b051f6ab 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -259,21 +259,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(store.0))) .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(store.0))) .collect() } diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs index 556b2ae9d9e5..5de68b74c474 100644 --- a/crates/wasmtime/src/component/func/host.rs +++ b/crates/wasmtime/src/component/func/host.rs @@ -85,7 +85,7 @@ impl HostFunc { let types = types.clone(); move |expected_index, expected_types| { - if index == expected_index && std::ptr::eq(&*types, expected_types.types) { + if index == expected_index && std::ptr::eq(&*types, &**expected_types.types) { Ok(()) } else { Err(anyhow!("function type mismatch")) @@ -385,11 +385,12 @@ where } closure(store.as_context_mut(), &args, &mut result_vals)?; flags.set_may_leave(false); - for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) { - Type::from(ty, types).check(val)?; - } 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, &instance).check(val)?; + } 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()) { diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 7efb096f910c..35ba1e549d7e 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -266,8 +266,14 @@ impl<'a, T> LowerContext<'a, T> { unsafe { (*self.instance).resource_lower_own(ty, rep) } } + /// TODO pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { - InstanceType::new(self.store.0, unsafe { &*self.instance }).resource_type(ty) + self.instance_type().resource_type(ty) + } + + /// TODO + pub fn instance_type(&self) -> InstanceType<'_> { + InstanceType::new(self.store.0, unsafe { &*self.instance }) } } @@ -338,7 +344,13 @@ impl<'a> LiftContext<'a> { unsafe { (*self.instance).resource_lift_own(ty, idx) } } + /// TODO pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { - InstanceType::new(self.store, unsafe { &*self.instance }).resource_type(ty) + self.instance_type().resource_type(ty) + } + + /// TODO + pub fn instance_type(&self) -> InstanceType<'_> { + InstanceType::new(self.store, unsafe { &*self.instance }) } } diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 0ee795ef87f2..ba131b174498 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -230,6 +230,14 @@ impl InstanceData { pub fn ty(&self, store: &StoreOpaque) -> InstanceType<'_> { InstanceType::new(store, self.instance()) } + + // TODO: NB: only during creation + fn resource_types_mut(&mut self) -> &mut PrimaryMap { + Arc::get_mut(self.state.resource_types_mut()) + .unwrap() + .downcast_mut() + .unwrap() + } } struct Instantiator<'a> { @@ -284,7 +292,7 @@ impl<'a> Instantiator<'a> { component: component.clone(), state: OwnedComponentInstance::new( component.runtime_info(), - Box::new(imported_resources), + Arc::new(imported_resources), store.traitobj(), ), imports: imports.clone(), @@ -295,19 +303,13 @@ impl<'a> Instantiator<'a> { fn run(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> { let env_component = self.component.env_component(); for (idx, import) in env_component.imported_resources.iter() { - let imported_resources = self - .data - .state - .imported_resources_mut() - .downcast_mut::() - .unwrap(); let (ty, func_ref) = match &self.imports[*import] { RuntimeImport::Resource { ty, dtor_funcref, .. } => (*ty, NonNull::from(dtor_funcref)), _ => unreachable!(), }; - let i = imported_resources.push(ty); + let i = self.data.resource_types_mut().push(ty); assert_eq!(i, idx); // TODO: this should be `Some` self.data.state.set_resource_destructor(idx, Some(func_ref)); @@ -475,6 +477,9 @@ impl<'a> Instantiator<'a> { .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) { diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs index b42046339ad3..602b9ab52bbe 100644 --- a/crates/wasmtime/src/component/matching.rs +++ b/crates/wasmtime/src/component/matching.rs @@ -1,11 +1,11 @@ use crate::component::func::HostFunc; -use crate::component::instance::ImportedResources; use crate::component::linker::{Definition, NameMap, Strings}; use crate::component::ResourceType; -use crate::store::{StoreId, StoreOpaque}; +use crate::store::StoreOpaque; 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, ResourceIndex, TypeComponentInstance, TypeDef, TypeFuncIndex, TypeModule, @@ -18,15 +18,14 @@ pub struct TypeChecker<'a> { pub types: &'a Arc, pub component: &'a wasmtime_environ::component::Component, pub strings: &'a Strings, - pub imported_resources: PrimaryMap, + pub imported_resources: Arc>, } #[derive(Copy, Clone)] #[doc(hidden)] pub struct InstanceType<'a> { - pub instance: Option<(StoreId, &'a ComponentInstance)>, - pub types: &'a ComponentTypes, - pub imported_resources: &'a ImportedResources, + pub types: &'a Arc, + pub resources: &'a Arc>, } impl TypeChecker<'_> { @@ -65,7 +64,9 @@ impl TypeChecker<'_> { // checked in the same order they were assigned into the // `Component` type. None => { - let id = self.imported_resources.push(*actual); + // TODO comment unwrap/get_mut + let resources = Arc::get_mut(&mut self.imported_resources).unwrap(); + let id = resources.push(*actual); assert_eq!(id, i); } @@ -159,8 +160,7 @@ impl TypeChecker<'_> { fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> { let instance_type = InstanceType { types: self.types, - imported_resources: &self.imported_resources, - instance: None, + resources: &self.imported_resources, }; actual.typecheck(expected, &instance_type) } @@ -185,21 +185,29 @@ impl Definition { } impl<'a> InstanceType<'a> { - pub fn new(store: &StoreOpaque, instance: &'a ComponentInstance) -> InstanceType<'a> { + pub fn new(_store: &StoreOpaque, instance: &'a ComponentInstance) -> InstanceType<'a> { InstanceType { - instance: Some((store.id(), instance)), types: instance.component_types(), - imported_resources: instance.imported_resources().downcast_ref().unwrap(), + resources: downcast_arc_ref(instance.resource_types()), } } pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType { let index = self.types[index].ty; - if let Some((store, instance)) = self.instance { - if let Some(index) = instance.component().defined_resource_index(index) { - return ResourceType::guest(store, instance, index); - } - } - self.imported_resources[index] + 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/types.rs b/crates/wasmtime/src/component/types.rs index 37f75aeadbc7..7ddfc1e21c38 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,10 +8,11 @@ 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; @@ -18,6 +20,24 @@ pub use crate::component::resources::ResourceType; 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 { @@ -33,7 +53,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) } } @@ -49,16 +71,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()) } } @@ -80,18 +99,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()), }) } } @@ -106,11 +122,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. @@ -118,7 +131,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())) } } @@ -140,18 +153,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())), }) } } @@ -166,11 +179,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. @@ -192,11 +202,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. @@ -204,7 +211,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())) } } @@ -218,16 +225,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()) } } @@ -241,18 +245,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(), )) } @@ -260,7 +261,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(), )) } } @@ -275,11 +276,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. @@ -321,6 +319,8 @@ pub enum Type { Option(OptionType), Result(ResultType), Flags(Flags), + Own(ResourceType), + Borrow(ResourceType), } impl Type { @@ -441,6 +441,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 { @@ -460,7 +484,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, @@ -475,18 +499,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::Own(id) => todo!(), - InterfaceType::Borrow(id) => todo!(), + 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)), } } @@ -514,6 +537,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 3e5f80cf625c..27bc7dd77d82 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -677,7 +677,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 +685,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 +701,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 +715,7 @@ impl Val { )?; Val::Enum(Enum { - ty: types::Enum::from(i, cx.types), + ty: types::Enum::from(i, &cx.instance_type()), discriminant, }) } @@ -728,7 +728,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 +742,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 +757,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 +770,7 @@ impl Val { .collect::>()?; Val::Flags(Flags { - ty: types::Flags::from(i, cx.types), + ty: types::Flags::from(i, &cx.instance_type()), count, value, }) @@ -805,11 +805,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) => { @@ -818,7 +818,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, }) @@ -829,7 +829,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, }) } @@ -839,7 +839,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, }) @@ -850,7 +850,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, }) @@ -861,13 +861,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([]), @@ -1204,7 +1204,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( diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 11cbdf54cbc2..1f55d9a701cd 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -469,3 +469,49 @@ fn manually_destroy() -> Result<()> { 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 (own $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(()) +} From 25c1b28ea6dfcf7526304ba4f3cea8f2ea949239 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 6 Jul 2023 10:18:40 -0700 Subject: [PATCH 14/47] Implement `Val::Own` --- crates/runtime/src/send_sync_ptr.rs | 8 +++ crates/wasmtime/src/component/resources.rs | 68 ++++++++++++++++------ crates/wasmtime/src/component/values.rs | 50 ++++++++++++++-- crates/wast/src/component.rs | 1 + tests/all/component_model/resources.rs | 62 ++++++++++++++++++++ 5 files changed, 168 insertions(+), 21 deletions(-) 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/component/resources.rs b/crates/wasmtime/src/component/resources.rs index 282b68b24302..2eb4b3e829ff 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -5,9 +5,9 @@ use crate::store::StoreId; use crate::{AsContextMut, StoreContextMut}; use anyhow::{bail, Result}; use std::any::TypeId; -use std::cell::Cell; use std::marker; use std::mem::MaybeUninit; +use std::sync::atomic::{AtomicU64, Ordering::Relaxed}; use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType}; use wasmtime_runtime::component::ComponentInstance; use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw}; @@ -55,7 +55,7 @@ enum ResourceTypeKind { /// TODO pub struct Resource { - rep: Cell>, + rep: ResourceRep, _marker: marker::PhantomData T>, } @@ -63,7 +63,7 @@ impl Resource { /// TODO pub fn new(rep: u32) -> Resource { Resource { - rep: Cell::new(Some(rep)), + rep: ResourceRep::new(rep), _marker: marker::PhantomData, } } @@ -81,10 +81,7 @@ impl Resource { InterfaceType::Own(t) => t, _ => bad_type_info(), }; - let rep = match self.rep.replace(None) { - Some(rep) => rep, - None => bail!("resource already consumed"), - }; + let rep = self.rep.take()?; Ok(cx.resource_lower_own(resource, rep)) } @@ -156,7 +153,7 @@ unsafe impl Lift for Resource { #[derive(Debug)] pub struct ResourceAny { store: StoreId, - rep: Cell>, + rep: ResourceRep, ty: ResourceType, dtor: Option>, } @@ -194,10 +191,7 @@ impl ResourceAny { fn resource_drop_impl(self, store: &mut StoreContextMut<'_, T>) -> Result<()> { assert_eq!(store.0.id(), self.store); - let rep = match self.rep.replace(None) { - Some(rep) => rep, - None => bail!("resource already consumed"), - }; + let rep = self.rep.take()?; let dtor = match self.dtor { Some(dtor) => dtor.as_non_null(), None => return Ok(()), @@ -215,10 +209,7 @@ impl ResourceAny { if cx.resource_type(resource) != self.ty { bail!("mismatched resource types") } - let rep = match self.rep.replace(None) { - Some(rep) => rep, - None => bail!("resource already consumed"), - }; + let rep = self.rep.take()?; Ok(cx.resource_lower_own(resource, rep)) } @@ -231,11 +222,26 @@ impl ResourceAny { let ty = cx.resource_type(resource); Ok(ResourceAny { store: cx.store.id(), - rep: Cell::new(Some(rep)), + rep: ResourceRep::new(rep), ty, dtor: dtor.map(SendSyncPtr::new), }) } + + /// TODO + pub(crate) fn partial_eq_for_val(&self, other: &ResourceAny) -> bool { + self.store == other.store && self.ty == other.ty && self.rep.get() == other.rep.get() + } + + /// TODO + pub(crate) fn clone_for_val(&self) -> ResourceAny { + ResourceAny { + store: self.store, + ty: self.ty, + rep: ResourceRep::empty(), + dtor: None, + } + } } unsafe impl ComponentType for ResourceAny { @@ -284,3 +290,31 @@ unsafe impl Lift for ResourceAny { ResourceAny::lift_from_index(cx, ty, index) } } + +/// TODO +#[derive(Debug)] +struct ResourceRep(AtomicU64); + +impl ResourceRep { + fn new(rep: u32) -> ResourceRep { + ResourceRep(AtomicU64::new((u64::from(rep) << 1) | 1)) + } + + fn empty() -> ResourceRep { + ResourceRep(AtomicU64::new(0)) + } + + fn take(&self) -> Result { + match self.0.swap(0, Relaxed) { + 0 => bail!("resource already consumed"), + n => Ok((n >> 1) as u32), + } + } + + fn get(&self) -> Option { + match self.0.load(Relaxed) { + 0 => None, + n => Some((n >> 1) as u32), + } + } +} diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index 27bc7dd77d82..586cbf6cab56 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,8 +592,13 @@ impl fmt::Debug for Flags { } } -/// Represents possible runtime values which a component function can either consume or produce -#[derive(Debug, Clone)] +/// Represents possible runtime values which a component function can either +/// consume or produce +/// +/// TODO: fill in notes on Clone +/// +/// TODO: fill in notes on PartialEq +#[derive(Debug)] #[allow(missing_docs)] pub enum Val { Bool(bool), @@ -617,6 +623,7 @@ pub enum Val { Option(OptionVal), Result(ResultVal), Flags(Flags), + Own(ResourceAny), } impl Val { @@ -645,6 +652,7 @@ 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::Own(r) => Type::Own(r.ty()), } } @@ -667,6 +675,7 @@ 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(_) => Val::Own(ResourceAny::lift(cx, ty, next(src))?), InterfaceType::String => { Val::String(Box::::lift(cx, ty, &[*next(src), *next(src)])?) } @@ -776,7 +785,6 @@ impl Val { }) } - InterfaceType::Own(i) => todo!(), InterfaceType::Borrow(i) => todo!(), }) } @@ -797,6 +805,7 @@ 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(_) => Val::Own(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; @@ -889,7 +898,6 @@ impl Val { }, }), - InterfaceType::Own(i) => todo!(), InterfaceType::Borrow(i) => todo!(), }) } @@ -914,6 +922,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::Own(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)?; @@ -988,6 +997,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::Own(value) => value.store(cx, ty, offset)?, Val::List(List { values, .. }) => { let ty = match ty { InterfaceType::List(i) => &cx.types[i], @@ -1124,12 +1134,44 @@ impl PartialEq for Val { (Self::Result(_), _) => false, (Self::Flags(l), Self::Flags(r)) => l == r, (Self::Flags(_), _) => false, + (Self::Own(l), Self::Own(r)) => l.partial_eq_for_val(r), + (Self::Own(_), _) => false, } } } impl Eq for Val {} +impl Clone for Val { + fn clone(&self) -> Val { + match self { + Self::Bool(v) => Self::Bool(*v), + Self::U8(v) => Self::U8(*v), + Self::S8(v) => Self::S8(*v), + Self::U16(v) => Self::U16(*v), + Self::S16(v) => Self::S16(*v), + Self::U32(v) => Self::U32(*v), + Self::S32(v) => Self::S32(*v), + Self::U64(v) => Self::U64(*v), + Self::S64(v) => Self::S64(*v), + Self::Float32(v) => Self::Float32(*v), + Self::Float64(v) => Self::Float64(*v), + Self::Char(v) => Self::Char(*v), + Self::String(s) => Self::String(s.clone()), + Self::List(s) => Self::List(s.clone()), + Self::Record(s) => Self::Record(s.clone()), + Self::Tuple(s) => Self::Tuple(s.clone()), + Self::Variant(s) => Self::Variant(s.clone()), + Self::Enum(s) => Self::Enum(s.clone()), + Self::Union(s) => Self::Union(s.clone()), + Self::Flags(s) => Self::Flags(s.clone()), + Self::Option(s) => Self::Option(s.clone()), + Self::Result(s) => Self::Result(s.clone()), + Self::Own(s) => Self::Own(s.clone_for_val()), + } + } +} + struct GenericVariant<'a> { discriminant: u32, payload: Option<(&'a Val, InterfaceType)>, diff --git a/crates/wast/src/component.rs b/crates/wast/src/component.rs index 425689de49ca..b8438d16b20c 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::Own(..) => "own", }; bail!("expected `{expected}` got `{actual}`") } diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 1f55d9a701cd..9811e03c4021 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -515,3 +515,65 @@ fn dynamic_type() -> Result<()> { 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,) = a_typed.call(&mut store, (Resource::new(100),))?; + a_typed.post_return(&mut store)?; + assert_eq!(t1.ty(), ResourceType::host::()); + + let mut results = [Val::Bool(false)]; + a.call(&mut store, &[Val::Own(t1)], &mut results)?; + a.post_return(&mut store)?; + match &results[0] { + Val::Own(resource) => { + assert_eq!(resource.ty(), ResourceType::host::()); + } + _ => unreachable!(), + } + + b.call(&mut store, &[Val::U32(200)], &mut results)?; + match &results[0] { + Val::Own(resource) => { + assert_eq!(resource.ty(), t2); + } + _ => unreachable!(), + } + + Ok(()) +} From a67737d770042097dafa942cead787adead8d22b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 6 Jul 2023 11:57:16 -0700 Subject: [PATCH 15/47] Implement reentrance check for destructors Implemented both in the raw wasm intrinsic as well as the host. --- crates/cranelift-shared/src/lib.rs | 3 + crates/cranelift/src/compiler/component.rs | 46 ++++++++++++-- crates/environ/src/component/dfg.rs | 3 + crates/environ/src/component/info.rs | 4 ++ .../environ/src/component/translate/inline.rs | 6 +- crates/environ/src/component/types.rs | 2 + .../environ/src/component/types/resources.rs | 16 ++++- crates/environ/src/trap_encoding.rs | 8 +++ crates/runtime/src/component.rs | 41 +++++++------ crates/wasmtime/src/component/func.rs | 2 +- crates/wasmtime/src/component/func/options.rs | 4 +- crates/wasmtime/src/component/resources.rs | 34 +++++++++-- tests/all/component_model/func.rs | 9 ++- tests/all/component_model/import.rs | 12 ++-- tests/all/component_model/post_return.rs | 27 ++++---- tests/all/component_model/resources.rs | 61 ++++++++++++++++++- 16 files changed, 216 insertions(+), 62 deletions(-) diff --git a/crates/cranelift-shared/src/lib.rs b/crates/cranelift-shared/src/lib.rs index 42a26352ca07..c67b9c2233a8 100644 --- a/crates/cranelift-shared/src/lib.rs +++ b/crates/cranelift-shared/src/lib.rs @@ -58,6 +58,8 @@ const DEBUG_ASSERT_TRAP_CODE: u16 = u16::MAX; /// Code used as the user-defined trap code. pub const ALWAYS_TRAP_CODE: u16 = 100; +/// Code used as the user-defined trap code. +pub const CANNOT_ENTER_CODE: u16 = 101; /// Converts machine traps to trap information. pub fn mach_trap_to_trap(trap: &MachTrap) -> Option { @@ -77,6 +79,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/component.rs b/crates/cranelift/src/compiler/component.rs index 1362db0b47f1..bc5e73c9b50e 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -6,7 +6,7 @@ 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_cranelift_shared::{ALWAYS_TRAP_CODE, CANNOT_ENTER_CODE}; use wasmtime_environ::component::*; use wasmtime_environ::{PtrSize, WasmFuncType, WasmType}; @@ -358,11 +358,19 @@ impl Compiler { let should_run_destructor = builder.func.dfg.inst_results(call)[0]; let resource_ty = types[resource.resource].ty; - let has_destructor = match component.defined_resource_index(resource_ty) { - Some(idx) => component.initializers.iter().any(|i| match i { - GlobalInitializer::Resource(r) => r.index == idx && r.dtor.is_some(), - _ => false, - }), + 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, }; if has_destructor { @@ -372,6 +380,12 @@ impl Compiler { // 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 + // // rep = ushr.i64 rep, 1 // rep = ireduce.i32 rep // dtor = load.ptr vmctx+$offset @@ -404,6 +418,26 @@ impl Compiler { 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)); + } + } 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; diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index 42c4b2e782f0..6d6f0b3319bf 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -267,6 +267,7 @@ pub struct Transcoder { 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 @@ -386,6 +387,7 @@ impl ComponentDfg { 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(), } } @@ -465,6 +467,7 @@ impl LinearizeDfg<'_> { dtor, index, rep: resource.rep, + instance: resource.instance, })); } diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 68f6a86c4ca3..9f589bfce216 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -160,6 +160,8 @@ pub struct Component { pub num_resources: u32, /// TODO pub imported_resources: PrimaryMap, + /// TODO + pub defined_resource_instances: PrimaryMap, } impl Component { @@ -558,6 +560,8 @@ pub struct Resource { pub rep: WasmType, /// TODO pub dtor: Option, + /// TODO + pub instance: RuntimeComponentInstanceIndex, } /// TODO diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 4252331726e6..78a42c0f0632 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -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 @@ -74,6 +76,7 @@ pub(super) fn run( // 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); for init in result.initializers.iter() { let (name, ty) = match *init { LocalInitializer::Import(name, ty) => (name, ty), @@ -115,7 +118,6 @@ 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 frame = InlinerFrame::new(index, result, ComponentClosure::default(), args, None); let resources_snapshot = types.resources_mut().clone(); @@ -340,6 +342,7 @@ impl<'a> Inliner<'a> { // TODO: comments about resources_cache loop { 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 @@ -559,6 +562,7 @@ impl<'a> Inliner<'a> { 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 diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 0c0448e7b397..1451dc682e15 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -1530,6 +1530,8 @@ pub struct TypeResult { pub struct TypeResourceTable { /// TODO pub ty: ResourceIndex, + /// TODO + pub instance: RuntimeComponentInstanceIndex, } /// Shape of a "list" interface type. diff --git a/crates/environ/src/component/types/resources.rs b/crates/environ/src/component/types/resources.rs index 22f86e889d82..a19e12861be4 100644 --- a/crates/environ/src/component/types/resources.rs +++ b/crates/environ/src/component/types/resources.rs @@ -1,4 +1,7 @@ -use crate::component::{ComponentTypes, ResourceIndex, TypeResourceTable, TypeResourceTableIndex}; +use crate::component::{ + ComponentTypes, ResourceIndex, RuntimeComponentInstanceIndex, TypeResourceTable, + TypeResourceTableIndex, +}; use std::collections::HashMap; use wasmparser::types; @@ -8,6 +11,7 @@ use wasmparser::types; pub struct ResourcesBuilder { resource_id_to_table_index: HashMap, resource_id_to_resource_index: HashMap, + current_instance: Option, } impl ResourcesBuilder { @@ -22,7 +26,10 @@ impl ResourcesBuilder { .entry(id) .or_insert_with(|| { let ty = self.resource_id_to_resource_index[&id]; - types.resource_tables.push(TypeResourceTable { ty }) + let instance = self.current_instance.expect("current instance not set"); + types + .resource_tables + .push(TypeResourceTable { ty, instance }) }) } @@ -79,4 +86,9 @@ impl ResourcesBuilder { let prev = self.resource_id_to_resource_index.insert(id, ty); assert!(prev.is_none()); } + + /// TODO + pub fn set_current_instance(&mut self, instance: RuntimeComponentInstanceIndex) { + self.current_instance = Some(instance); + } } 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/runtime/src/component.rs b/crates/runtime/src/component.rs index b7245de44b22..cc3678bfaa03 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -224,10 +224,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())) } } @@ -565,7 +565,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 @@ -660,11 +660,16 @@ impl ComponentInstance { &mut self, ty: TypeResourceTableIndex, idx: u32, - ) -> Result<(u32, Option>)> { + ) -> Result<(u32, Option>, Option)> { let rep = self.resource_tables.resource_lift_own(ty, idx)?; let resource = self.component_types()[ty].ty; let dtor = self.resource_destructor(resource); - Ok((rep, dtor)) + let component = self.component(); + let flags = component.defined_resource_index(resource).map(|i| { + let instance = component.defined_resource_instances[i]; + self.instance_flags(instance) + }); + Ok((rep, dtor, flags)) } /// TODO @@ -937,55 +942,55 @@ impl VMOpaqueContext { #[allow(missing_docs)] #[repr(transparent)] -pub struct InstanceFlags(*mut VMGlobalDefinition); +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/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 3074b051f6ab..2075a9e64690 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -467,7 +467,7 @@ 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); diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 35ba1e549d7e..23c093073965 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -6,7 +6,7 @@ use anyhow::{bail, Result}; use std::ptr::NonNull; use std::sync::Arc; use wasmtime_environ::component::{ComponentTypes, StringEncoding, TypeResourceTableIndex}; -use wasmtime_runtime::component::ComponentInstance; +use wasmtime_runtime::component::{ComponentInstance, InstanceFlags}; use wasmtime_runtime::{VMFuncRef, VMMemoryDefinition}; /// Runtime representation of canonical ABI options in the component model. @@ -339,7 +339,7 @@ impl<'a> LiftContext<'a> { &self, ty: TypeResourceTableIndex, idx: u32, - ) -> Result<(u32, Option>)> { + ) -> Result<(u32, Option>, Option)> { // TODO: document unsafe unsafe { (*self.instance).resource_lift_own(ty, idx) } } diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index 2eb4b3e829ff..c27d39d0f13e 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -2,14 +2,15 @@ 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; -use crate::{AsContextMut, StoreContextMut}; +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::{AtomicU64, Ordering::Relaxed}; use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType}; -use wasmtime_runtime::component::ComponentInstance; +use wasmtime_runtime::component::{ComponentInstance, InstanceFlags}; use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw}; /// TODO @@ -90,7 +91,9 @@ impl Resource { InterfaceType::Own(t) => t, _ => bad_type_info(), }; - let (rep, _dtor) = cx.resource_lift_own(resource, index)?; + let (rep, dtor, flags) = cx.resource_lift_own(resource, index)?; + debug_assert!(flags.is_none()); + debug_assert!(dtor.is_some()); // TODO: should debug assert types match here Ok(Resource::new(rep)) } @@ -150,12 +153,12 @@ unsafe impl Lift for Resource { } /// TODO -#[derive(Debug)] pub struct ResourceAny { store: StoreId, rep: ResourceRep, ty: ResourceType, dtor: Option>, + flags: Option, } impl ResourceAny { @@ -192,6 +195,16 @@ impl ResourceAny { fn resource_drop_impl(self, store: &mut StoreContextMut<'_, T>) -> Result<()> { assert_eq!(store.0.id(), self.store); let rep = self.rep.take()?; + + // TODO + if let Some(flags) = &self.flags { + unsafe { + if !flags.may_enter() { + bail!(Trap::CannotEnterComponent); + } + } + } + let dtor = match self.dtor { Some(dtor) => dtor.as_non_null(), None => return Ok(()), @@ -218,13 +231,14 @@ impl ResourceAny { InterfaceType::Own(t) => t, _ => bad_type_info(), }; - let (rep, dtor) = cx.resource_lift_own(resource, index)?; + let (rep, dtor, flags) = cx.resource_lift_own(resource, index)?; let ty = cx.resource_type(resource); Ok(ResourceAny { store: cx.store.id(), rep: ResourceRep::new(rep), ty, dtor: dtor.map(SendSyncPtr::new), + flags, }) } @@ -240,6 +254,7 @@ impl ResourceAny { ty: self.ty, rep: ResourceRep::empty(), dtor: None, + flags: None, } } } @@ -292,7 +307,6 @@ unsafe impl Lift for ResourceAny { } /// TODO -#[derive(Debug)] struct ResourceRep(AtomicU64); impl ResourceRep { @@ -318,3 +332,11 @@ impl ResourceRep { } } } + +impl fmt::Debug for ResourceAny { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ResourceAny") + .field("rep", &self.rep.get()) + .finish() + } +} diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 59fda76da7f5..3a3bfe8e3899 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -2384,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 index 9811e03c4021..d0c34b8d0ebe 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -1,6 +1,6 @@ use anyhow::Result; use wasmtime::component::*; -use wasmtime::Store; +use wasmtime::{Store, Trap}; #[test] fn host_resource_types() -> Result<()> { @@ -577,3 +577,62 @@ fn dynamic_val() -> Result<()> { 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(()) +} From 7c0b9a26de90699c2fc9e1588e1fe840a6560b0e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jul 2023 13:32:13 -0700 Subject: [PATCH 16/47] Use cast instead of transmute --- crates/wasmtime/src/component/component.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index 4ca6db9d4196..35703f45f8b9 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -399,12 +399,7 @@ impl Component { .info .resource_drop_wasm_to_native_trampoline .as_ref() - .map(|i| { - let ptr = self.func(i); - unsafe { - mem::transmute::, NonNull>(ptr) - } - }); + .map(|i| self.func(i).cast()); VMFuncRef { wasm_call, ..*dtor.func_ref() From 9c21f23832668843d117fcf3765ad0b4541b50e8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jul 2023 13:33:44 -0700 Subject: [PATCH 17/47] Fill out some cranelift-shared comments --- crates/cranelift-shared/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/cranelift-shared/src/lib.rs b/crates/cranelift-shared/src/lib.rs index c67b9c2233a8..add380288703 100644 --- a/crates/cranelift-shared/src/lib.rs +++ b/crates/cranelift-shared/src/lib.rs @@ -56,9 +56,15 @@ 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; -/// Code used as the user-defined trap code. + +/// 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. From 725ddc2403dce53bf5c7aa8602ddacc829f17c2c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jul 2023 13:37:14 -0700 Subject: [PATCH 18/47] Update codegen for resource.drop shim The MAY_ENTER flag must always be checked, regardless of whether there's an actual destructor or not. --- crates/cranelift/src/compiler/component.rs | 151 +++++++++++---------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index b00d798dca11..8cb9cbb513c4 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -373,71 +373,80 @@ impl Compiler { Some(def) => def.dtor.is_some(), None => true, }; - if has_destructor { - // 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 - // - // 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, - &[], - ); + // 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)); - } + 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; @@ -472,17 +481,13 @@ impl Compiler { 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); - } else { - // If this resource has no destructor, then after the table is - // updated there's nothing to do, so we can return. - builder.ins().return_(&[]); } + 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()?)) From 4d6b2bfcdda94f50d9cb49ee0667c829b0746d1a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jul 2023 14:06:30 -0700 Subject: [PATCH 19/47] Update wasm-tools crates to latest `main` --- Cargo.lock | 51 +++++++++++-------- Cargo.toml | 2 +- cranelift/wasm/src/sections_translator.rs | 12 +++-- crates/environ/examples/factc.rs | 7 +-- crates/environ/src/component/translate.rs | 32 +++++------- .../environ/src/component/translate/inline.rs | 13 ++--- crates/environ/src/component/types.rs | 46 +++++------------ .../environ/src/component/types/resources.rs | 17 ++----- crates/environ/src/module_environ.rs | 14 +++-- winch/codegen/src/codegen/env.rs | 6 +-- winch/filetests/src/lib.rs | 6 +-- winch/src/compile.rs | 6 +-- 12 files changed, 95 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61fd9d5d84e0..a2f56b7b2ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3718,8 +3718,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.29.1" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.30.0" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "leb128", ] @@ -3733,47 +3742,47 @@ dependencies = [ "anyhow", "indexmap 1.9.1", "serde", - "wasm-encoder 0.29.1", + "wasm-encoder 0.29.0", "wasmparser 0.107.0", ] [[package]] name = "wasm-metadata" version = "0.9.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "anyhow", "indexmap 2.0.0", "serde", "serde_json", "spdx", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasmparser 0.108.0", ] [[package]] name = "wasm-mutate" version = "0.2.28" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "egg", "log", "rand 0.8.5", "thiserror", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasmparser 0.108.0", ] [[package]] name = "wasm-smith" version = "0.12.11" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "arbitrary", "flagset", "indexmap 2.0.0", "leb128", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasmparser 0.108.0", ] @@ -3837,7 +3846,7 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.108.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "indexmap 2.0.0", "semver", @@ -3855,7 +3864,7 @@ dependencies = [ [[package]] name = "wasmprinter" version = "0.2.60" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "anyhow", "wasmparser 0.108.0", @@ -4000,7 +4009,7 @@ dependencies = [ "test-programs", "tokio", "walkdir", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasmparser 0.108.0", "wasmtime", "wasmtime-cache", @@ -4105,7 +4114,7 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasmparser 0.108.0", "wasmprinter", "wasmtime-component-util", @@ -4196,7 +4205,7 @@ dependencies = [ "target-lexicon", "tempfile", "v8", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasm-mutate", "wasm-smith", "wasm-spec-interpreter", @@ -4420,18 +4429,18 @@ dependencies = [ [[package]] name = "wast" version = "60.0.1" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", ] [[package]] name = "wat" version = "1.0.67" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "wast 60.0.1", ] @@ -4824,7 +4833,7 @@ dependencies = [ "bitflags 1.3.2", "indexmap 1.9.1", "log", - "wasm-encoder 0.29.1", + "wasm-encoder 0.29.0", "wasm-metadata 0.8.0", "wasmparser 0.107.0", "wit-parser 0.8.0", @@ -4833,13 +4842,13 @@ dependencies = [ [[package]] name = "wit-component" version = "0.12.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "anyhow", "bitflags 1.3.2", "indexmap 2.0.0", "log", - "wasm-encoder 0.29.1", + "wasm-encoder 0.30.0", "wasm-metadata 0.9.0", "wasmparser 0.108.0", "wit-parser 0.9.0", @@ -4864,7 +4873,7 @@ dependencies = [ [[package]] name = "wit-parser" version = "0.9.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#bd1d0618a487cd88a97f37ef027b07d3c2e25662" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" dependencies = [ "anyhow", "id-arena", diff --git a/Cargo.toml b/Cargo.toml index 3352ec2d306d..459e6687f084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -204,7 +204,7 @@ wasmparser = "0.108.0" wat = "1.0.67" wast = "60.0.1" wasmprinter = "0.2.59" -wasm-encoder = "0.29.1" +wasm-encoder = "0.30.0" wasm-smith = "0.12.11" wasm-mutate = "0.2.28" wit-parser = "0.9.0" diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index 73e9f945a22c..e6aff9706545 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -21,7 +21,7 @@ use wasmparser::{ self, Data, DataKind, DataSectionReader, Element, ElementItems, ElementKind, ElementSectionReader, Export, ExportSectionReader, ExternalKind, FunctionSectionReader, GlobalSectionReader, ImportSectionReader, MemorySectionReader, MemoryType, NameSectionReader, - Naming, Operator, TableSectionReader, TagSectionReader, TagType, Type, TypeRef, + Naming, Operator, StructuralType, TableSectionReader, TagSectionReader, TagType, TypeRef, TypeSectionReader, }; @@ -51,12 +51,16 @@ pub fn parse_type_section<'a>( environ.reserve_types(count)?; for entry in types { - match entry? { - Type::Func(wasm_func_ty) => { + let entry = entry?; + if entry.is_final || entry.supertype_idx.is_some() { + unimplemented!("gc proposal"); + } + match entry.structural_type { + StructuralType::Func(wasm_func_ty) => { let ty = environ.convert_func_type(&wasm_func_ty); environ.declare_type_func(ty)?; } - Type::Array(_) | Type::Struct(_) => { + StructuralType::Array(_) | StructuralType::Struct(_) => { unimplemented!("gc proposal"); } } diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs index e0f144a604bb..7be55a39741a 100644 --- a/crates/environ/examples/factc.rs +++ b/crates/environ/examples/factc.rs @@ -128,11 +128,8 @@ impl Factc { .validate_all(&input) .context("failed to validate input wasm")?; let wasm_types = wasm_types.as_ref(); - for i in 0.. { - let ty = match wasm_types.id_from_type_index(i, false) { - Some(ty) => ty, - None => break, - }; + for i in 0..wasm_types.component_type_count() { + let ty = wasm_types.component_type_at(i); let ty = types.convert_component_func_type(wasm_types, ty)?; adapters.push(Adapter { lift_ty: ty, diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 1f5d4611ff2a..7b24d8d25b67 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -402,9 +402,7 @@ impl<'a, 'data> Translator<'a, 'data> { match ty? { wasmparser::ComponentType::Resource { rep, dtor } => { let rep = self.types.convert_valtype(rep); - let id = types - .id_from_type_index(component_type_index, false) - .unwrap(); + let id = types.component_type_at(component_type_index); let dtor = dtor.map(FuncIndex::from_u32); self.result .initializers @@ -455,7 +453,7 @@ impl<'a, 'data> Translator<'a, 'data> { core_func_index, options, } => { - let ty = types.id_from_type_index(type_index, false).unwrap(); + let ty = types.component_type_at(type_index); let func = FuncIndex::from_u32(core_func_index); let options = self.canonical_options(&options); LocalInitializer::Lift(ty, func, options) @@ -464,7 +462,7 @@ impl<'a, 'data> Translator<'a, 'data> { func_index, options, } => { - let lower_ty = types.component_function_at(func_index).unwrap(); + let lower_ty = types.component_function_at(func_index); let func = ComponentFuncIndex::from_u32(func_index); let options = self.canonical_options(&options); let canonical_abi = self.core_func_signature(core_func_index); @@ -478,23 +476,19 @@ impl<'a, 'data> Translator<'a, 'data> { } } wasmparser::CanonicalFunction::ResourceNew { resource } => { - let resource = types.id_from_type_index(resource, false).unwrap(); + 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 { ty } => { let ty = match ty { - wasmparser::ComponentValType::Type(t) => { - types.id_from_type_index(t, false).unwrap() - } + wasmparser::ComponentValType::Type(t) => types.component_type_at(t), wasmparser::ComponentValType::Primitive(_) => unreachable!(), }; - let resource = match types.type_from_id(ty) { - Some(types::Type::Defined( - types::ComponentDefinedType::Own(id) - | types::ComponentDefinedType::Borrow(id), - )) => *id, + let resource = match types[ty].unwrap_defined() { + types::ComponentDefinedType::Own(id) + | types::ComponentDefinedType::Borrow(id) => *id, _ => unreachable!(), }; let ty = self.core_func_signature(core_func_index); @@ -502,7 +496,7 @@ impl<'a, 'data> Translator<'a, 'data> { LocalInitializer::ResourceDrop(resource, ty) } wasmparser::CanonicalFunction::ResourceRep { resource } => { - let resource = types.id_from_type_index(resource, false).unwrap(); + let resource = types.component_type_at(resource); let ty = self.core_func_signature(core_func_index); core_func_index += 1; LocalInitializer::ResourceRep(resource, ty) @@ -581,7 +575,7 @@ impl<'a, 'data> Translator<'a, 'data> { args, } => { let types = self.validator.types(0).unwrap(); - let ty = types.component_instance_at(index).unwrap(); + let ty = types.component_instance_at(index); let index = ComponentIndex::from_u32(component_index); self.instantiate_component(index, &args, ty)? } @@ -777,7 +771,7 @@ impl<'a, 'data> Translator<'a, 'data> { } wasmparser::ComponentExternalKind::Type => { let types = self.validator.types(0).unwrap(); - let ty = types.id_from_type_index(index, false).unwrap(); + let ty = types.component_type_at(index); ComponentItem::Type(ty) } }) @@ -885,8 +879,8 @@ impl<'a, 'data> Translator<'a, 'data> { fn core_func_signature(&mut self, idx: u32) -> SignatureIndex { let types = self.validator.types(0).unwrap(); - let id = types.function_at(idx).unwrap(); - let ty = types.type_from_id(id).unwrap().as_func_type().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) } diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 78a42c0f0632..57b37a3d3fb2 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -77,6 +77,7 @@ pub(super) fn run( 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 { LocalInitializer::Import(name, ty) => (name, ty), @@ -86,7 +87,7 @@ pub(super) fn run( // TODO: comment this let index = inliner.result.import_types.next_key(); types.resources_mut().register_component_entity_type( - result.types_ref(), + &types_ref, ty, &mut path, &mut |path| { @@ -98,7 +99,7 @@ pub(super) fn run( }, ); - let ty = types.convert_component_entity_type(result.types_ref(), ty)?; + let ty = types.convert_component_entity_type(types_ref, ty)?; // Imports of types are not required to be specified by the host since // it's just for type information within the component. Note that this @@ -412,8 +413,8 @@ impl<'a> Inliner<'a> { None => { match ty { ComponentEntityType::Type { created, .. } => { - match frame.translation.types_ref().type_from_id(*created) { - Some(wasmparser::types::Type::Resource(_)) => unreachable!(), + match frame.translation.types_ref()[*created] { + wasmparser::types::Type::Resource(_) => unreachable!(), _ => {} } } @@ -426,7 +427,7 @@ impl<'a> Inliner<'a> { let (resources, types) = types.resources_mut_and_types(); resources.register_component_entity_type( - frame.translation.types_ref(), + &frame.translation.types_ref(), *ty, &mut path, &mut |path| arg.lookup_resource(path, types), @@ -1114,7 +1115,7 @@ impl<'a> InlinerFrame<'a> { let mut path = Vec::new(); let arg = ComponentItemDef::Instance(def); resources.register_component_entity_type( - self.translation.types_ref(), + &self.translation.types_ref(), wasmparser::types::ComponentEntityType::Instance(ty), &mut path, &mut |path| arg.lookup_resource(path, types), diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 1451dc682e15..7767e8552e6e 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -420,11 +420,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - let ty = types - .type_from_id(id) - .unwrap() - .as_component_func_type() - .unwrap(); + let ty = types[id].unwrap_component_func(); let params = ty .params .iter() @@ -462,15 +458,11 @@ impl ComponentTypesBuilder { types::ComponentEntityType::Func(id) => { TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) } - types::ComponentEntityType::Type { created, .. } => { - match types.type_from_id(created).unwrap() { - 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::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"), }) } @@ -481,7 +473,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - Ok(match types.type_from_id(id).unwrap() { + Ok(match types[id] { types::Type::Defined(_) => TypeDef::Interface(self.defined_type(types, id)?), types::Type::Module(_) => TypeDef::Module(self.convert_module(types, id)?), types::Type::Component(_) => TypeDef::Component(self.convert_component(types, id)?), @@ -491,10 +483,7 @@ impl ComponentTypesBuilder { types::Type::ComponentFunc(_) => { TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) } - types::Type::Instance(_) - | types::Type::Func(_) - | types::Type::Array(_) - | types::Type::Struct(_) => { + types::Type::Instance(_) | types::Type::Sub(_) => { unreachable!() } types::Type::Resource(_) => TypeDef::Resource(self.resource_id(types, id)), @@ -506,7 +495,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - let ty = &types.type_from_id(id).unwrap().as_component_type().unwrap(); + let ty = types[id].unwrap_component(); let mut result = TypeComponent::default(); for (name, ty) in ty.imports.iter() { result.imports.insert( @@ -528,11 +517,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - let ty = &types - .type_from_id(id) - .unwrap() - .as_component_instance_type() - .unwrap(); + let ty = types[id].unwrap_component_instance(); let mut result = TypeComponentInstance::default(); for (name, ty) in ty.exports.iter() { result.exports.insert( @@ -548,7 +533,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - let ty = &types.type_from_id(id).unwrap().as_module_type().unwrap(); + let ty = &types[id].unwrap_module(); let mut result = TypeModule::default(); for ((module, field), ty) in ty.imports.iter() { result.imports.insert( @@ -571,7 +556,7 @@ impl ComponentTypesBuilder { ) -> Result { Ok(match ty { types::EntityType::Func(idx) => { - let ty = types.type_from_id(*idx).unwrap().as_func_type().unwrap(); + let ty = types[*idx].unwrap_func(); let ty = self.convert_func_type(ty); EntityType::Function(self.module_types_builder().wasm_func_type(ty)) } @@ -587,7 +572,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> Result { - let ret = match types.type_from_id(id).unwrap().as_defined_type().unwrap() { + let ret = match types[id].unwrap_defined() { types::ComponentDefinedType::Primitive(ty) => ty.into(), types::ComponentDefinedType::Record(e) => { InterfaceType::Record(self.record_type(types, e)?) @@ -787,10 +772,7 @@ impl ComponentTypesBuilder { types: types::TypesRef<'_>, id: types::TypeId, ) -> TypeResourceTableIndex { - let id = match types.type_from_id(id) { - Some(wasmparser::types::Type::Resource(id)) => *id, - _ => unreachable!(), - }; + let id = types[id].unwrap_resource(); self.resources.convert(id, &mut self.component_types) } diff --git a/crates/environ/src/component/types/resources.rs b/crates/environ/src/component/types/resources.rs index a19e12861be4..cf13b7363cc6 100644 --- a/crates/environ/src/component/types/resources.rs +++ b/crates/environ/src/component/types/resources.rs @@ -36,18 +36,14 @@ impl ResourcesBuilder { /// TODO pub fn register_component_entity_type<'a>( &mut self, - types: types::TypesRef<'a>, + types: &'a types::TypesRef<'_>, ty: types::ComponentEntityType, path: &mut Vec<&'a str>, register: &mut dyn FnMut(&[&'a str]) -> ResourceIndex, ) { match ty { types::ComponentEntityType::Instance(id) => { - let ty = types - .type_from_id(id) - .unwrap() - .as_component_instance_type() - .unwrap(); + 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); @@ -55,8 +51,8 @@ impl ResourcesBuilder { } } types::ComponentEntityType::Type { created, .. } => { - let id = match types.type_from_id(created).unwrap() { - types::Type::Resource(id) => *id, + let id = match types[created] { + types::Type::Resource(id) => id, _ => return, }; self.resource_id_to_resource_index @@ -79,10 +75,7 @@ impl ResourcesBuilder { id: types::TypeId, ty: ResourceIndex, ) { - let id = match types.type_from_id(id).unwrap() { - types::Type::Resource(id) => *id, - _ => unreachable!(), - }; + let id = types[id].unwrap_resource(); let prev = self.resource_id_to_resource_index.insert(id, ty); assert!(prev.is_none()); } diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index cb36343c3fba..670d764f012a 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -16,8 +16,8 @@ use std::path::PathBuf; use std::sync::Arc; use wasmparser::{ types::Types, CustomSectionReader, DataKind, ElementItems, ElementKind, Encoding, ExternalKind, - FuncToValidate, FunctionBody, NameSectionReader, Naming, Operator, Parser, Payload, Type, - TypeRef, Validator, ValidatorResources, + FuncToValidate, FunctionBody, NameSectionReader, Naming, Operator, Parser, Payload, + StructuralType, TypeRef, Validator, ValidatorResources, }; /// Object containing the standalone environment information. @@ -238,12 +238,16 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { self.types.reserve_wasm_signatures(num); for ty in types { - match ty? { - Type::Func(wasm_func_ty) => { + let ty = ty?; + if ty.is_final || ty.supertype_idx.is_some() { + unimplemented!("gc proposal") + } + match ty.structural_type { + StructuralType::Func(wasm_func_ty) => { let ty = self.convert_func_type(&wasm_func_ty); self.declare_type_func(ty)?; } - Type::Array(_) | Type::Struct(_) => { + StructuralType::Array(_) | StructuralType::Struct(_) => { unimplemented!("gc proposal") } } diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs index 5ff5b9db0a04..b5c41106acb8 100644 --- a/winch/codegen/src/codegen/env.rs +++ b/winch/codegen/src/codegen/env.rs @@ -29,10 +29,8 @@ impl<'a, P: PtrSize> FuncEnv<'a, P> { /// Resolves a function [`Callee`] from an index. pub fn callee_from_index(&self, idx: FuncIndex) -> Callee { let types = &self.translation.get_types(); - let id = types - .function_at(idx.as_u32()) - .unwrap_or_else(|| panic!("function type at index: {}", idx.as_u32())); - let ty = types.type_from_id(id).unwrap().as_func_type().unwrap(); + let id = types.function_at(idx.as_u32()); + let ty = types[id].unwrap_func(); let ty = self.translation.module.convert_func_type(ty); let import = self.translation.module.is_imported_function(idx); diff --git a/winch/filetests/src/lib.rs b/winch/filetests/src/lib.rs index e30692394664..71fd35f48cbe 100644 --- a/winch/filetests/src/lib.rs +++ b/winch/filetests/src/lib.rs @@ -154,10 +154,8 @@ mod test { let types = &translation.get_types(); let index = module.func_index(f.0); - let id = types - .function_at(index.as_u32()) - .expect(&format!("function type at index {:?}", index.as_u32())); - let sig = types.type_from_id(id).unwrap().as_func_type().unwrap(); + let id = types.function_at(index.as_u32()); + let sig = types[id].unwrap_func(); let sig = translation.module.convert_func_type(&sig); let FunctionBodyData { body, validator } = f.1; diff --git a/winch/src/compile.rs b/winch/src/compile.rs index d09d7ed4708a..fb15bc3b0ece 100644 --- a/winch/src/compile.rs +++ b/winch/src/compile.rs @@ -53,10 +53,8 @@ fn compile( ) -> Result<()> { let index = translation.module.func_index(f.0); let types = &translation.get_types(); - let id = types - .function_at(index.as_u32()) - .expect(&format!("function type at index {:?}", index.as_u32())); - let sig = types.type_from_id(id).unwrap().as_func_type().unwrap(); + let id = types.function_at(index.as_u32()); + let sig = types[id].unwrap_func(); let sig = translation.module.convert_func_type(sig); let FunctionBodyData { body, validator } = f.1; let mut validator = validator.into_validator(Default::default()); From 1f7696fba23faa70fe976678b6e7fa09c158d8f5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jul 2023 14:53:26 -0700 Subject: [PATCH 20/47] Update resource.drop binary format --- Cargo.lock | 20 ++++++------- crates/environ/src/component/translate.rs | 14 ++------- tests/all/component_model/resources.rs | 16 +++++----- .../component-model/resources.wast | 30 +++++++++---------- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2f56b7b2ee9..48d8f6f0780d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3728,7 +3728,7 @@ dependencies = [ [[package]] name = "wasm-encoder" version = "0.30.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "leb128", ] @@ -3749,7 +3749,7 @@ dependencies = [ [[package]] name = "wasm-metadata" version = "0.9.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "anyhow", "indexmap 2.0.0", @@ -3763,7 +3763,7 @@ dependencies = [ [[package]] name = "wasm-mutate" version = "0.2.28" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "egg", "log", @@ -3776,7 +3776,7 @@ dependencies = [ [[package]] name = "wasm-smith" version = "0.12.11" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "arbitrary", "flagset", @@ -3846,7 +3846,7 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.108.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "indexmap 2.0.0", "semver", @@ -3864,7 +3864,7 @@ dependencies = [ [[package]] name = "wasmprinter" version = "0.2.60" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "anyhow", "wasmparser 0.108.0", @@ -4429,7 +4429,7 @@ dependencies = [ [[package]] name = "wast" version = "60.0.1" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "leb128", "memchr", @@ -4440,7 +4440,7 @@ dependencies = [ [[package]] name = "wat" version = "1.0.67" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "wast 60.0.1", ] @@ -4842,7 +4842,7 @@ dependencies = [ [[package]] name = "wit-component" version = "0.12.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -4873,7 +4873,7 @@ dependencies = [ [[package]] name = "wit-parser" version = "0.9.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#f1ad425a595a059d618d62c38ed899befe2b8765" +source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" dependencies = [ "anyhow", "id-arena", diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 7b24d8d25b67..0cbfb6c2bea3 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -8,7 +8,7 @@ use anyhow::{bail, Result}; use indexmap::IndexMap; use std::collections::HashMap; use std::mem; -use wasmparser::types::{self, ComponentEntityType, TypeId, Types}; +use wasmparser::types::{ComponentEntityType, TypeId, Types}; use wasmparser::{Chunk, ComponentExternName, Encoding, Parser, Payload, Validator}; mod adapt; @@ -481,16 +481,8 @@ impl<'a, 'data> Translator<'a, 'data> { core_func_index += 1; LocalInitializer::ResourceNew(resource, ty) } - wasmparser::CanonicalFunction::ResourceDrop { ty } => { - let ty = match ty { - wasmparser::ComponentValType::Type(t) => types.component_type_at(t), - wasmparser::ComponentValType::Primitive(_) => unreachable!(), - }; - let resource = match types[ty].unwrap_defined() { - types::ComponentDefinedType::Own(id) - | types::ComponentDefinedType::Borrow(id) => *id, - _ => unreachable!(), - }; + 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) diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index d0c34b8d0ebe..264fdc74c382 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -120,8 +120,8 @@ fn resource_any() -> Result<()> { (func (export "[constructor]u") (param "x" u32) (result (own $u)) (canon lift (core func $u_ctor))) - (core func $t_drop (canon resource.drop (own $t))) - (core func $u_drop (canon resource.drop (own $u))) + (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))) @@ -224,7 +224,7 @@ fn mismatch_resource_types() -> Result<()> { (func (export "ctor") (param "x" u32) (result (own $t)) (canon lift (core func $t_ctor))) - (core func $u_dtor (canon resource.drop (own $u))) + (core func $u_dtor (canon resource.drop $u)) (func (export "dtor") (param "x" (own $u)) (canon lift (core func $u_dtor))) ) @@ -261,13 +261,13 @@ fn drop_in_different_places() -> Result<()> { (func (export "ctor") (param "x" u32) (result (own $t)) (canon lift (core func $ctor))) - (core func $dtor (canon resource.drop (own $t))) + (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 (own $t))) + (core func $dtor (canon resource.drop $t)) (func (export "dtor") (param "x" (own $t)) (canon lift (core func $dtor))) ) @@ -320,7 +320,7 @@ fn drop_guest_twice() -> Result<()> { (func (export "ctor") (param "x" u32) (result (own $t)) (canon lift (core func $ctor))) - (core func $dtor (canon resource.drop (own $t))) + (core func $dtor (canon resource.drop $t)) (func (export "dtor") (param "x" (own $t)) (canon lift (core func $dtor))) ) @@ -354,7 +354,7 @@ fn drop_host_twice() -> Result<()> { (component (import "t" (type $t (sub resource))) - (core func $dtor (canon resource.drop (own $t))) + (core func $dtor (canon resource.drop $t)) (func (export "dtor") (param "x" (own $t)) (canon lift (core func $dtor))) ) @@ -480,7 +480,7 @@ fn dynamic_type() -> Result<()> { (import "t1" (type $t1 (sub resource))) (type $t2' (resource (rep i32))) (export $t2 "t2" (type $t2')) - (core func $f (canon resource.drop (own $t2))) + (core func $f (canon resource.drop $t2)) (func (export "a") (param "x" (own $t1)) (canon lift (core func $f))) diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index c89a44d0c116..ecfc70b6d08f 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -3,7 +3,7 @@ (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 (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "rep" (func $rep (param i32) (result i32))) @@ -34,7 +34,7 @@ ;; cannot call `resource.drop` on a nonexistent resource (component (type $r (resource (rep i32))) - (core func $drop (canon resource.drop (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "drop" (func $drop (param i32))) @@ -80,7 +80,7 @@ (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 (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "rep" (func $rep (param i32) (result i32))) @@ -225,7 +225,7 @@ (alias export $host "[constructor]resource1" (func $ctor)) (alias export $host "[static]resource1.assert" (func $assert)) - (core func $drop (canon resource.drop (own $r))) + (core func $drop (canon resource.drop $r)) (core func $ctor (canon lower (func $ctor))) (core func $assert (canon lower (func $assert))) @@ -344,7 +344,7 @@ (alias export $host "resource1" (type $r)) (alias export $host "[constructor]resource1" (func $ctor)) - (core func $drop (canon resource.drop (own $r))) + (core func $drop (canon resource.drop $r)) (core func $ctor (canon lower (func $ctor))) (core module $m @@ -412,7 +412,7 @@ ;; 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 (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "drop" (func $drop (param i32))) @@ -458,7 +458,7 @@ ;; 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 (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "drop" (func $drop (param i32))) @@ -485,7 +485,7 @@ (type $r (resource (rep i32))) (core func $ctor (canon resource.new $r)) - (core func $drop (canon resource.drop (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "ctor" (func $ctor (param i32) (result i32))) @@ -545,7 +545,7 @@ (import "[constructor]r" (func $ctor (param "r" u32) (result (own $r)))) (core func $ctor (canon lower (func $ctor))) - (core func $drop (canon resource.drop (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "ctor" (func $ctor (param i32) (result i32))) @@ -600,8 +600,8 @@ (core func $new1 (canon resource.new $r)) (core func $new2 (canon resource.new $r)) - (core func $drop1 (canon resource.drop (own $r))) - (core func $drop2 (canon resource.drop (own $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))) @@ -643,7 +643,7 @@ (component (type $r (resource (rep i32))) - (core func $drop (canon resource.drop (own $r))) + (core func $drop (canon resource.drop $r)) (core module $m (import "" "drop" (func $drop (param i32))) @@ -680,8 +680,8 @@ (type $r1 (resource (rep i32))) (type $r2 (resource (rep i32) (dtor (func $i1 "dtor")))) - (core func $drop1 (canon resource.drop (own $r1))) - (core func $drop2 (canon resource.drop (own $r2))) + (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)) @@ -755,7 +755,7 @@ (alias export $host "[static]resource1.last-drop" (func $last-drop)) (alias export $host "[static]resource1.drops" (func $drops)) - (core func $drop (canon resource.drop (own $r))) + (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))) From 8fa6b8c0898ca3e49c508ed869295434dc30f19b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jul 2023 14:53:37 -0700 Subject: [PATCH 21/47] Add some docs --- crates/environ/src/component.rs | 3 +- crates/environ/src/component/compiler.rs | 14 +++++-- crates/environ/src/component/dfg.rs | 41 ++++++++++++++---- crates/environ/src/component/info.rs | 53 ++++++++++++++++++------ 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index aeb8b5c6bf29..880ee9bacae8 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -71,7 +71,8 @@ macro_rules! foreach_transcoder { }; } -/// TODO +/// Helper macro, like `foreach_transcoder`, to iterate over builtins for +/// components unrelated to transcoding. #[macro_export] macro_rules! foreach_builtin_component_function { ($mac:ident) => { diff --git a/crates/environ/src/component/compiler.rs b/crates/environ/src/component/compiler.rs index 8f5e65ac6a58..b8256845a621 100644 --- a/crates/environ/src/component/compiler.rs +++ b/crates/environ/src/component/compiler.rs @@ -84,7 +84,13 @@ pub trait ComponentCompiler: Send + Sync { types: &ComponentTypes, ) -> Result>>; - /// TODO + /// 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, @@ -92,7 +98,7 @@ pub trait ComponentCompiler: Send + Sync { types: &ComponentTypes, ) -> Result>>; - /// TODO + /// Same as `compile_resource_new` except for the `resource.rep` intrinsic. fn compile_resource_rep( &self, component: &Component, @@ -100,7 +106,9 @@ pub trait ComponentCompiler: Send + Sync { types: &ComponentTypes, ) -> Result>>; - /// TODO + /// 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, diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index 6d6f0b3319bf..48a527992cca 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -109,23 +109,48 @@ pub struct ComponentDfg { /// version of the adapter. pub adapter_paritionings: PrimaryMap, - /// TODO + /// 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, - /// TODO + /// 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, - /// TODO + /// 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, } -/// TODO +/// Possible side effects that are possible with instantiating this component. pub enum SideEffect { - /// TODO + /// 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), - /// TODO + + /// 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), } @@ -391,7 +416,8 @@ impl ComponentDfg { } } - /// TODO + /// 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)) } @@ -407,7 +433,6 @@ struct LinearizeDfg<'a> { runtime_always_trap: HashMap, runtime_lowerings: HashMap, runtime_transcoders: HashMap, - // runtime_resources: HashSet, runtime_resource_new: HashMap, runtime_resource_rep: HashMap, runtime_resource_drop: HashMap, diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 9f589bfce216..602761edec77 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -148,24 +148,42 @@ pub struct Component { /// modules. pub num_transcoders: u32, - /// TODO + /// Number of `ResourceNew` initializers in the global initializers list. pub num_resource_new: u32, - /// TODO + + /// Number of `ResourceRep` initializers in the global initializers list. pub num_resource_rep: u32, - /// TODO + + /// Number of `ResourceDrop` initializers in the global initializers list. pub num_resource_drop: u32, - /// TODO + + /// Maximal number of tables that required at runtime for resource-related + /// information in this component. pub num_resource_tables: usize, - /// TODO + + /// Total number of resources both imported and defined within this + /// component. pub num_resources: u32, - /// TODO + + /// 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, - /// TODO + + /// 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 { - /// TODO + /// 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() @@ -173,7 +191,8 @@ impl Component { Some(DefinedResourceIndex::from_u32(idx)) } - /// TODO + /// 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()) } @@ -236,13 +255,21 @@ pub enum GlobalInitializer { /// used to instantiate an adapter module. Transcoder(Transcoder), - /// TODO + /// Declares a new defined resource within this component. + /// + /// Contains information about the destructor, for example. Resource(Resource), - /// TODO + + /// Declares a new `resource.new` intrinsic should be initialized. + /// + /// This will initialize a `VMFuncRef` within the `VMComponentContext` for + /// the described resource. ResourceNew(ResourceNew), - /// TODO + + /// Same as `ResourceNew`, but for `resource.rep` intrinsics. ResourceRep(ResourceRep), - /// TODO + + /// Same as `ResourceNew`, but for `resource.drop` intrinsics. ResourceDrop(ResourceDrop), } From 8f1708d58c94a0750fa512d7b3592caa11cd40fc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Jul 2023 09:00:33 -0700 Subject: [PATCH 22/47] Implement dynamic tracking for borrow resources Not actually hooked up anywhere but this should at least be a first stab at an implementation of the spec. --- crates/runtime/src/component.rs | 38 ++++ crates/runtime/src/component/resources.rs | 201 ++++++++++++++++++---- 2 files changed, 210 insertions(+), 29 deletions(-) diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index cc3678bfaa03..418d5683038e 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -672,6 +672,34 @@ impl ComponentInstance { Ok((rep, dtor, flags)) } + /// TODO + pub fn resource_lift_borrow(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { + self.resource_tables.resource_lift_borrow(ty, idx) + } + + /// TODO + pub fn resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> Result { + // 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. + let resource = &self.component_types()[ty]; + let component = self.component(); + if let Some(idx) = component.defined_resource_index(resource.ty) { + if resource.instance == component.defined_resource_instances[idx] { + return Ok(rep); + } + } + + // ... failing that though borrow tracking enuses and is delegated to + // the resource tables implementation. + self.resource_tables.resource_lower_borrow(ty, rep) + } + /// TODO pub fn resource_rep32(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result { self.resource_tables.resource_rep(resource, idx) @@ -685,6 +713,16 @@ impl ComponentInstance { ) -> Result> { self.resource_tables.resource_drop(resource, idx) } + + /// TODO + pub fn enter_call(&mut self) { + self.resource_tables.enter_call(); + } + + /// TODO + pub fn exit_call(&mut self) -> Result<()> { + self.resource_tables.exit_call() + } } impl VMComponentContext { diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index f9b64d4303d7..fda4bcfae87c 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -5,17 +5,31 @@ use wasmtime_environ::PrimaryMap; pub struct ResourceTables { tables: PrimaryMap, + calls: Vec, } #[derive(Default)] struct ResourceTable { - next: usize, - slots: Vec>, + next: u32, + slots: Vec, } -enum Slot { - Free { next: usize }, - Taken(T), +enum Slot { + Free { next: u32 }, + Own { rep: u32, lend_count: u32 }, + Borrow { rep: u32, scope: usize }, +} + +#[derive(Default)] +struct CallContext { + lenders: Vec, + borrow_count: u32, +} + +#[derive(Copy, Clone)] +pub struct Lender { + ty: TypeResourceTableIndex, + idx: u32, } impl ResourceTables { @@ -24,62 +38,191 @@ impl ResourceTables { for _ in 0..amt { tables.push(ResourceTable::default()); } - ResourceTables { tables } + ResourceTables { + tables, + calls: Vec::new(), + } } + /// Implementation of the `resource.new` canonical intrinsic. + /// + /// Note that this is the same as `resource_lower_own`. pub fn resource_new(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { - self.tables[ty].insert(rep) + self.tables[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: TypeResourceTableIndex, idx: u32) -> Result { - self.tables[ty].get(idx) + match self.tables[ty].get_mut(idx)? { + Slot::Own { rep, .. } | Slot::Borrow { rep, .. } => Ok(*rep), + Slot::Free { .. } => unreachable!(), + } } + /// 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: TypeResourceTableIndex, idx: u32) -> Result> { - let rep = self.tables[ty].remove(idx)?; - Ok(Some(rep)) + match self.tables[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[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: TypeResourceTableIndex, rep: u32) -> u32 { - // TODO: this impl should probably not be literally the same as `resource_new` - self.tables[ty].insert(rep) + self.tables[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: TypeResourceTableIndex, idx: u32) -> Result { - self.tables[ty].remove(idx) + match self.tables[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: TypeResourceTableIndex, idx: u32) -> Result { + match self.tables[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(); + self.calls + .last_mut() + .unwrap() + .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: TypeResourceTableIndex, rep: u32) -> Result { + let scope = self.calls.len() - 1; + let borrow_count = &mut self.calls.last_mut().unwrap().borrow_count; + *borrow_count = borrow_count.checked_add(1).unwrap(); + Ok(self.tables[ty].insert(Slot::Borrow { rep, scope })) + } + + pub fn enter_call(&mut self) { + self.calls.push(CallContext::default()); + } + + pub fn exit_call(&mut self) -> Result<()> { + let cx = self.calls.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.tables[lender.ty].get_mut(lender.idx).unwrap() { + Slot::Own { lend_count, .. } => { + *lend_count -= 1; + } + _ => unreachable!(), + } + } + Ok(()) } } impl ResourceTable { - pub fn insert(&mut self, rep: u32) -> u32 { - if self.next == self.slots.len() { + 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 + 1, + next: self.next.checked_add(1).unwrap(), }); } let ret = self.next; - self.next = match mem::replace(&mut self.slots[self.next], Slot::Taken(rep)) { + self.next = match mem::replace(&mut self.slots[next], new) { Slot::Free { next } => next, - Slot::Taken(_) => unreachable!(), + _ => unreachable!(), }; u32::try_from(ret).unwrap() } - pub fn get(&mut self, idx: u32) -> Result { - match self.slots.get(idx as usize) { - Some(Slot::Taken(rep)) => Ok(*rep), - _ => bail!("unknown handle index {idx}"), + 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), } } - pub fn remove(&mut self, idx: u32) -> Result { - let rep = match self.slots.get(idx as usize) { - Some(Slot::Taken(rep)) => *rep, + 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}"), }; - // TODO: dtor called here - self.slots[idx as usize] = Slot::Free { next: self.next }; - self.next = idx as usize; - Ok(rep) + let ret = mem::replace( + &mut self.slots[idx as usize], + Slot::Free { next: self.next }, + ); + self.next = idx; + Ok(ret) } } From 4fbf2e71667a5ce52df90963204a965551b6f072 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Jul 2023 10:41:57 -0700 Subject: [PATCH 23/47] Remove git overrides --- Cargo.lock | 40 +++++++++++++++++++--------------------- Cargo.toml | 12 ------------ 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3db1ff2900c3..092871bed8fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3728,7 +3728,8 @@ dependencies = [ [[package]] name = "wasm-encoder" version = "0.30.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f8e9778e04cbf44f58acc301372577375a666b966c50b03ef46144f80436a8" dependencies = [ "leb128", ] @@ -3749,7 +3750,8 @@ dependencies = [ [[package]] name = "wasm-metadata" version = "0.9.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51db59397fc650b5f2fc778e4a5c4456cd856bed7fc1ec15f8d3e28229dc463" dependencies = [ "anyhow", "indexmap 2.0.0", @@ -3763,7 +3765,8 @@ dependencies = [ [[package]] name = "wasm-mutate" version = "0.2.28" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97aa47493e9a6d9615d99866bc13cbf0ae38fb5b9c4846a13feeb68191d6051a" dependencies = [ "egg", "log", @@ -3776,7 +3779,8 @@ dependencies = [ [[package]] name = "wasm-smith" version = "0.12.11" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a683710b09c4d35e9bf711a0f116cabde0f13a505ea6d7b5aea74b5a150367" dependencies = [ "arbitrary", "flagset", @@ -3846,7 +3850,8 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.108.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c956109dcb41436a39391139d9b6e2d0a5e0b158e1293ef352ec977e5e36c5" dependencies = [ "indexmap 2.0.0", "semver", @@ -3864,7 +3869,8 @@ dependencies = [ [[package]] name = "wasmprinter" version = "0.2.60" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b76cb909fe3d9b0de58cee1f4072247e680ff5cc1558ccad2790a9de14a23993" dependencies = [ "anyhow", "wasmparser 0.108.0", @@ -4426,17 +4432,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wast" -version = "60.0.1" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" -dependencies = [ - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.30.0", -] - [[package]] name = "wast" version = "61.0.0" @@ -4452,9 +4447,10 @@ dependencies = [ [[package]] name = "wat" version = "1.0.67" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459e764d27c3ab7beba1ebd617cc025c7e76dea6e7c5ce3189989a970aea3491" dependencies = [ - "wast 60.0.1", + "wast 61.0.0", ] [[package]] @@ -4854,7 +4850,8 @@ dependencies = [ [[package]] name = "wit-component" version = "0.12.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "253bd426c532f1cae8c633c517c63719920535f3a7fada3589de40c5b734e393" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -4885,7 +4882,8 @@ dependencies = [ [[package]] name = "wit-parser" version = "0.9.0" -source = "git+https://github.com/alexcrichton/wasm-tools?branch=wasmtime-resources#b0e98eeb28ed3d5328fbc94e0ef473d35acfaf8c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f2afd756820d516d4973f67a739ca5529cc872d80114be17d4bba79375981c" dependencies = [ "anyhow", "id-arena", diff --git a/Cargo.toml b/Cargo.toml index b5374f407644..f0ec104381ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -312,15 +312,3 @@ debug-assertions = false # Omit integer overflow checks, which include failure messages which require # string initializers. overflow-checks = false - -[patch.crates-io] -wat = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wast = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wasmparser = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wasmprinter = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wasm-mutate = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wasm-smith = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wasm-encoder = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wasm-metadata = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wit-component = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } -wit-parser = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'wasmtime-resources' } From 136ced3709248ad74ca3c8f6dfab3ba295f8bb78 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Jul 2023 10:42:51 -0700 Subject: [PATCH 24/47] Remove no-longer-needed arms in wit-bindgen --- crates/wit-bindgen/src/rust.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/wit-bindgen/src/rust.rs b/crates/wit-bindgen/src/rust.rs index 11f797d15b4e..94cc233f2f75 100644 --- a/crates/wit-bindgen/src/rust.rs +++ b/crates/wit-bindgen/src/rust.rs @@ -125,8 +125,6 @@ pub trait RustGenerator<'a> { } TypeDefKind::Type(Type::String) => true, TypeDefKind::Type(_) => false, - TypeDefKind::Resource => todo!(), - TypeDefKind::Handle(_) => todo!(), TypeDefKind::Unknown => unreachable!(), } } From a2c8772f26d4ef363f5c16cf382e188f5d6c1019 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Jul 2023 13:59:11 -0700 Subject: [PATCH 25/47] Prepare for mutability in `LiftContext` * Change `&LiftContext` to `&mut LiftContext` * Remove `store: &'a StoreOpaque` from `LiftContext`, instead storing just `memory: &'a [u8]` * Refactor methods to avoid needing the entire `StoreOpaque` This'll enable `LiftContext` to store `&'a mut ResourceTable` in an upcoming commit to refer to the host's resources. --- crates/component-macro/src/component.rs | 12 +- crates/misc/component-test-util/src/lib.rs | 4 +- crates/wasmtime/src/component/func.rs | 12 +- crates/wasmtime/src/component/func/host.rs | 15 +-- crates/wasmtime/src/component/func/options.rs | 31 +++-- crates/wasmtime/src/component/func/typed.rs | 121 ++++++++++-------- crates/wasmtime/src/component/instance.rs | 8 +- crates/wasmtime/src/component/matching.rs | 3 +- crates/wasmtime/src/component/resources.rs | 14 +- crates/wasmtime/src/component/values.rs | 12 +- 10 files changed, 125 insertions(+), 107 deletions(-) diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs index ae724ada15bc..b70c327534cc 100644 --- a/crates/component-macro/src/component.rs +++ b/crates/component-macro/src/component.rs @@ -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 { @@ -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/misc/component-test-util/src/lib.rs b/crates/misc/component-test-util/src/lib.rs index e9d63c095294..9f463a7ffac9 100644 --- a/crates/misc/component-test-util/src/lib.rs +++ b/crates/misc/component-test-util/src/lib.rs @@ -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/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 2075a9e64690..247d5de247b4 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -246,7 +246,7 @@ impl Func { let data = &store[self.0]; let cx = instance .unwrap_or_else(|| &store[data.instance.0].as_ref().unwrap()) - .ty(store); + .ty(); let ty = &cx.types[data.ty]; Params::typecheck(&InterfaceType::Tuple(ty.params), &cx) @@ -265,7 +265,7 @@ impl Func { data.types[data.types[data.ty].params] .types .iter() - .map(|ty| Type::from(ty, &instance.ty(store.0))) + .map(|ty| Type::from(ty, &instance.ty())) .collect() } @@ -277,7 +277,7 @@ impl Func { data.types[data.types[data.ty].results] .types .iter() - .map(|ty| Type::from(ty, &instance.ty(store.0))) + .map(|ty| Type::from(ty, &instance.ty())) .collect() } @@ -422,7 +422,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, @@ -516,7 +516,7 @@ impl Func { // later get used in post-return. flags.set_needs_post_return(true); let val = lift( - &LiftContext::new(store.0, &options, &types, instance_ptr), + &mut LiftContext::new(store.0, &options, &types, instance_ptr), InterfaceType::Tuple(types[ty].results), ret, )?; @@ -678,7 +678,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 5de68b74c474..8277063d0735 100644 --- a/crates/wasmtime/src/component/func/host.rs +++ b/crates/wasmtime/src/component/func/host.rs @@ -221,7 +221,7 @@ where } }; let params = storage.lift_params( - &LiftContext::new(cx.0, &options, types, instance), + &mut LiftContext::new(cx.0, &options, types, instance), param_tys, )?; @@ -248,7 +248,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), @@ -348,7 +348,7 @@ where let func_ty = &types[ty]; let param_tys = &types[func_ty.params]; let result_tys = &types[func_ty.results]; - let cx = LiftContext::new(store.0, &options, types, instance); + let mut cx = LiftContext::new(store.0, &options, types, instance); 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 = @@ -356,7 +356,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()); @@ -369,11 +369,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; diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 23c093073965..cc9fd1ead6e1 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -152,6 +152,11 @@ impl Options { pub fn string_encoding(&self) -> StringEncoding { self.string_encoding } + + /// TODO + pub fn store_id(&self) -> StoreId { + self.store_id + } } /// A helper structure which is a "package" of the context used during lowering @@ -263,6 +268,7 @@ impl<'a, T> LowerContext<'a, T> { /// TODO pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + // TODO: document unsafe unsafe { (*self.instance).resource_lower_own(ty, rep) } } @@ -273,7 +279,8 @@ impl<'a, T> LowerContext<'a, T> { /// TODO pub fn instance_type(&self) -> InstanceType<'_> { - InstanceType::new(self.store.0, unsafe { &*self.instance }) + // TODO: document unsafe + InstanceType::new(unsafe { &*self.instance }) } } @@ -284,16 +291,14 @@ 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, } @@ -305,13 +310,13 @@ impl<'a> LiftContext<'a> { /// /// TODO: ptr must be valid pub unsafe fn new( - store: &'a StoreOpaque, + store: &'a mut StoreOpaque, options: &'a Options, types: &'a Arc, instance: *mut ComponentInstance, ) -> LiftContext<'a> { LiftContext { - store, + memory: options.memory.map(|_| options.memory(store)), options, types, instance, @@ -326,7 +331,12 @@ 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() + } + + /// TODO + pub fn store_id(&self) -> StoreId { + self.options.store_id } /// TODO @@ -336,7 +346,7 @@ impl<'a> LiftContext<'a> { /// TODO pub fn resource_lift_own( - &self, + &mut self, ty: TypeResourceTableIndex, idx: u32, ) -> Result<(u32, Option>, Option)> { @@ -351,6 +361,7 @@ impl<'a> LiftContext<'a> { /// TODO pub fn instance_type(&self) -> InstanceType<'_> { - InstanceType::new(self.store, unsafe { &*self.instance }) + // TODO: document unsafe + InstanceType::new(unsafe { &*self.instance }) } } diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs index dfe840ac7875..cb5a64ec3985 100644 --- a/crates/wasmtime/src/component/func/typed.rs +++ b/crates/wasmtime/src/component/func/typed.rs @@ -1,7 +1,6 @@ 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::{AsContextMut, StoreContext, StoreContextMut, ValRaw}; use anyhow::{anyhow, bail, Context, Result}; use std::borrow::Cow; @@ -298,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 { @@ -308,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())?; @@ -527,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. /// @@ -543,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 @@ -610,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()) } } )*) @@ -631,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() } } )*) @@ -695,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())) @@ -777,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()))) @@ -837,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), @@ -846,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), @@ -895,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()); @@ -1095,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), @@ -1140,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( @@ -1178,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], )) @@ -1203,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(); @@ -1212,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 @@ -1342,7 +1344,7 @@ impl WasmList { fn new( ptr: usize, len: usize, - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, elem: InterfaceType, ) -> Result> { match len @@ -1377,21 +1379,28 @@ 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, mut store: impl AsContextMut, index: usize) -> Option> { - self.get_from_store(store.as_context_mut().0, index) + let store = store.as_context_mut().0; + self.options.store_id().assert_belongs_to(store.id()); + // TODO: comment unsafety, validity of `self.instance` carried over + 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, store: &StoreOpaque, index: usize) -> Option> { + fn get_from_store(&self, cx: &mut LiftContext<'_>, index: usize) -> Option> { if index >= self.len { return None; } - // TODO: comment unsafety, validity of `self.instance` carried over - let cx = - unsafe { LiftContext::new(store, &self.options, &self.types, self.instance.as_ptr()) }; // 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 @@ -1399,7 +1408,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. @@ -1411,7 +1420,11 @@ impl WasmList { 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()); + // TODO: comment unsafety, validity of `self.instance` carried over + 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()) } } @@ -1479,7 +1492,7 @@ unsafe impl ComponentType for WasmList { } 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(), @@ -1491,7 +1504,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(), @@ -1804,7 +1817,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(), @@ -1816,7 +1829,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, @@ -2060,7 +2073,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]; @@ -2094,7 +2107,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..]; @@ -2113,7 +2126,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, { @@ -2123,7 +2136,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, { @@ -2228,7 +2241,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(), @@ -2243,7 +2256,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, diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index ba131b174498..adf0f059bd22 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -227,8 +227,8 @@ impl InstanceData { self.component.types() } - pub fn ty(&self, store: &StoreOpaque) -> InstanceType<'_> { - InstanceType::new(store, self.instance()) + pub fn ty(&self) -> InstanceType<'_> { + InstanceType::new(self.instance()) } // TODO: NB: only during creation @@ -817,9 +817,7 @@ 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(self.store).resource_type(*id)) - } + Export::Type(TypeDef::Resource(id)) => Some(self.data.ty().resource_type(*id)), Export::Type(_) | Export::LiftedFunction { .. } | Export::ModuleStatic(_) diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs index 602b9ab52bbe..83f3348a778b 100644 --- a/crates/wasmtime/src/component/matching.rs +++ b/crates/wasmtime/src/component/matching.rs @@ -1,7 +1,6 @@ use crate::component::func::HostFunc; use crate::component::linker::{Definition, NameMap, Strings}; use crate::component::ResourceType; -use crate::store::StoreOpaque; use crate::types::matching; use crate::Module; use anyhow::{anyhow, bail, Context, Result}; @@ -185,7 +184,7 @@ impl Definition { } impl<'a> InstanceType<'a> { - pub fn new(_store: &StoreOpaque, instance: &'a ComponentInstance) -> InstanceType<'a> { + pub fn new(instance: &'a ComponentInstance) -> InstanceType<'a> { InstanceType { types: instance.component_types(), resources: downcast_arc_ref(instance.resource_types()), diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index c27d39d0f13e..d6e5bdfa76c4 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -86,7 +86,7 @@ impl Resource { Ok(cx.resource_lower_own(resource, rep)) } - fn lift_from_index(cx: &LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { + fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { let resource = match ty { InterfaceType::Own(t) => t, _ => bad_type_info(), @@ -141,12 +141,12 @@ unsafe impl Lower for Resource { } unsafe impl Lift for Resource { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + 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: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + 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) } @@ -226,7 +226,7 @@ impl ResourceAny { Ok(cx.resource_lower_own(resource, rep)) } - fn lift_from_index(cx: &LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { + fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { let resource = match ty { InterfaceType::Own(t) => t, _ => bad_type_info(), @@ -234,7 +234,7 @@ impl ResourceAny { let (rep, dtor, flags) = cx.resource_lift_own(resource, index)?; let ty = cx.resource_type(resource); Ok(ResourceAny { - store: cx.store.id(), + store: cx.store_id(), rep: ResourceRep::new(rep), ty, dtor: dtor.map(SendSyncPtr::new), @@ -295,12 +295,12 @@ unsafe impl Lower for ResourceAny { } unsafe impl Lift for ResourceAny { - fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result { + 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: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result { + 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) } diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index 586cbf6cab56..096ca2b67fab 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -658,7 +658,7 @@ impl Val { /// 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 { @@ -790,7 +790,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)?), @@ -1228,7 +1228,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(); @@ -1260,7 +1260,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> { @@ -1277,7 +1277,7 @@ fn load_record( } fn load_variant( - cx: &LiftContext<'_>, + cx: &mut LiftContext<'_>, info: &VariantInfo, mut types: impl ExactSizeIterator>, bytes: &[u8], @@ -1311,7 +1311,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>, From 099f1f927ac354512d697e573c6ec9dfcebde2f4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Jul 2023 09:12:25 -0700 Subject: [PATCH 26/47] Lowering a borrow is infallible --- crates/runtime/src/component.rs | 4 ++-- crates/runtime/src/component/resources.rs | 4 ++-- crates/wasmtime/src/component/func/options.rs | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index 418d5683038e..7eae3e0ef3ac 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -678,7 +678,7 @@ impl ComponentInstance { } /// TODO - pub fn resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> Result { + pub fn 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 @@ -691,7 +691,7 @@ impl ComponentInstance { let component = self.component(); if let Some(idx) = component.defined_resource_index(resource.ty) { if resource.instance == component.defined_resource_instances[idx] { - return Ok(rep); + return rep; } } diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index fda4bcfae87c..9811cad8a0c9 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -152,11 +152,11 @@ impl ResourceTables { /// 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: TypeResourceTableIndex, rep: u32) -> Result { + pub fn resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { let scope = self.calls.len() - 1; let borrow_count = &mut self.calls.last_mut().unwrap().borrow_count; *borrow_count = borrow_count.checked_add(1).unwrap(); - Ok(self.tables[ty].insert(Slot::Borrow { rep, scope })) + self.tables[ty].insert(Slot::Borrow { rep, scope }) } pub fn enter_call(&mut self) { diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index cc9fd1ead6e1..1e4d24254459 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -272,6 +272,12 @@ impl<'a, T> LowerContext<'a, T> { unsafe { (*self.instance).resource_lower_own(ty, rep) } } + /// TODO + pub fn resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + // TODO: document unsafe + unsafe { (*self.instance).resource_lower_borrow(ty, rep) } + } + /// TODO pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { self.instance_type().resource_type(ty) @@ -354,6 +360,12 @@ impl<'a> LiftContext<'a> { unsafe { (*self.instance).resource_lift_own(ty, idx) } } + /// TODO + pub fn resource_lift_borrow(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { + // TODO: document unsafe + unsafe { (*self.instance).resource_lift_borrow(ty, idx) } + } + /// TODO pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { self.instance_type().resource_type(ty) From 57025325df0bec8b905d7e6f6aa7492b35fa312a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Jul 2023 09:12:48 -0700 Subject: [PATCH 27/47] Use `ResourceAny` for both own/borrow Rename `Val::Own` to `Val::Resource` accordingly. --- crates/wasmtime/src/component/values.rs | 32 +++++++++++++++---------- crates/wast/src/component.rs | 2 +- tests/all/component_model/resources.rs | 6 ++--- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index 096ca2b67fab..defb187a969b 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -623,7 +623,7 @@ pub enum Val { Option(OptionVal), Result(ResultVal), Flags(Flags), - Own(ResourceAny), + Resource(ResourceAny), } impl Val { @@ -652,7 +652,13 @@ 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::Own(r) => Type::Own(r.ty()), + Val::Resource(r) => { + if r.owned() { + Type::Own(r.ty()) + } else { + Type::Borrow(r.ty()) + } + } } } @@ -675,7 +681,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(_) => Val::Own(ResourceAny::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)])?) } @@ -784,8 +792,6 @@ impl Val { value, }) } - - InterfaceType::Borrow(i) => todo!(), }) } @@ -805,7 +811,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(_) => Val::Own(ResourceAny::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; @@ -897,8 +905,6 @@ impl Val { .collect::>()?, }, }), - - InterfaceType::Borrow(i) => todo!(), }) } @@ -922,7 +928,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::Own(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)?; @@ -997,7 +1003,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::Own(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], @@ -1134,8 +1140,8 @@ impl PartialEq for Val { (Self::Result(_), _) => false, (Self::Flags(l), Self::Flags(r)) => l == r, (Self::Flags(_), _) => false, - (Self::Own(l), Self::Own(r)) => l.partial_eq_for_val(r), - (Self::Own(_), _) => false, + (Self::Resource(l), Self::Resource(r)) => l.partial_eq_for_val(r), + (Self::Resource(_), _) => false, } } } @@ -1167,7 +1173,7 @@ impl Clone for Val { Self::Flags(s) => Self::Flags(s.clone()), Self::Option(s) => Self::Option(s.clone()), Self::Result(s) => Self::Result(s.clone()), - Self::Own(s) => Self::Own(s.clone_for_val()), + Self::Resource(s) => Self::Resource(s.clone_for_val()), } } } diff --git a/crates/wast/src/component.rs b/crates/wast/src/component.rs index b8438d16b20c..4975009821d7 100644 --- a/crates/wast/src/component.rs +++ b/crates/wast/src/component.rs @@ -376,7 +376,7 @@ fn mismatch(expected: &WastVal<'_>, actual: &Val) -> Result<()> { Val::Option(..) => "option", Val::Result(..) => "result", Val::Flags(..) => "flags", - Val::Own(..) => "own", + Val::Resource(..) => "resource", }; bail!("expected `{expected}` got `{actual}`") } diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 264fdc74c382..0177db1a47cf 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -558,10 +558,10 @@ fn dynamic_val() -> Result<()> { assert_eq!(t1.ty(), ResourceType::host::()); let mut results = [Val::Bool(false)]; - a.call(&mut store, &[Val::Own(t1)], &mut results)?; + a.call(&mut store, &[Val::Resource(t1)], &mut results)?; a.post_return(&mut store)?; match &results[0] { - Val::Own(resource) => { + Val::Resource(resource) => { assert_eq!(resource.ty(), ResourceType::host::()); } _ => unreachable!(), @@ -569,7 +569,7 @@ fn dynamic_val() -> Result<()> { b.call(&mut store, &[Val::U32(200)], &mut results)?; match &results[0] { - Val::Own(resource) => { + Val::Resource(resource) => { assert_eq!(resource.ty(), t2); } _ => unreachable!(), From 23d72ac56eed73168278a9fb50e405e4a43b847f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Jul 2023 09:44:41 -0700 Subject: [PATCH 28/47] Initial implementation of borrowed resources Lots of juggling of contexts here and there to try and get everything working but this is hopefully a faithful implementation. Tests not implemented yet and will come next and additionally likely update implementation details as issues are weeded out. --- crates/runtime/src/component.rs | 103 +++--- crates/runtime/src/component/resources.rs | 118 ++++--- crates/runtime/src/lib.rs | 4 + crates/wasmtime/src/component/func/options.rs | 99 +++++- crates/wasmtime/src/component/resources.rs | 315 ++++++++++++------ crates/wasmtime/src/component/values.rs | 36 +- crates/wasmtime/src/store.rs | 29 ++ crates/wast/src/spectest.rs | 8 +- tests/all/component_model/resources.rs | 11 +- 9 files changed, 464 insertions(+), 259 deletions(-) diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index 7eae3e0ef3ac..d178b8aab5de 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -6,7 +6,6 @@ //! Eventually it's intended that module-to-module calls, which would be //! cranelift-compiled adapters, will use this `VMComponentContext` as well. -use self::resources::ResourceTables; use crate::{ SendSyncPtr, Store, VMArrayCallFunction, VMFuncRef, VMGlobalDefinition, VMMemoryDefinition, VMNativeCallFunction, VMOpaqueContext, VMSharedSignatureIndex, VMWasmCallFunction, ValRaw, @@ -22,13 +21,15 @@ use std::ops::Deref; use std::ptr::{self, NonNull}; use std::sync::Arc; use wasmtime_environ::component::*; -use wasmtime_environ::HostPtr; +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,7 +50,7 @@ pub struct ComponentInstance { runtime_info: Arc, /// TODO - resource_tables: ResourceTables, + component_resource_tables: PrimaryMap, /// TODO: /// TODO: Any is bad @@ -176,6 +177,12 @@ impl ComponentInstance { ) { 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 { @@ -189,7 +196,7 @@ impl ComponentInstance { ) .unwrap(), ), - resource_tables: ResourceTables::new(runtime_info.component().num_resource_tables), + component_resource_tables, runtime_info, resource_types, vmctx: VMComponentContext { @@ -546,7 +553,7 @@ impl ComponentInstance { } /// TODO - pub fn resource_destructor(&mut self, idx: ResourceIndex) -> Option> { + 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); @@ -647,62 +654,23 @@ impl ComponentInstance { /// TODO pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> u32 { - self.resource_tables.resource_new(resource, rep) - } - - /// TODO - pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { - self.resource_tables.resource_lower_own(ty, rep) - } - - /// TODO - pub fn resource_lift_own( - &mut self, - ty: TypeResourceTableIndex, - idx: u32, - ) -> Result<(u32, Option>, Option)> { - let rep = self.resource_tables.resource_lift_own(ty, idx)?; - 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) - }); - Ok((rep, dtor, flags)) + self.resource_tables().resource_new(Some(resource), rep) } /// TODO - pub fn resource_lift_borrow(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { - self.resource_tables.resource_lift_borrow(ty, idx) - } - - /// TODO - pub fn 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. + pub fn resource_owned_by_own_instance(&self, ty: TypeResourceTableIndex) -> bool { let resource = &self.component_types()[ty]; let component = self.component(); - if let Some(idx) = component.defined_resource_index(resource.ty) { - if resource.instance == component.defined_resource_instances[idx] { - return rep; - } - } - - // ... failing that though borrow tracking enuses and is delegated to - // the resource tables implementation. - self.resource_tables.resource_lower_borrow(ty, rep) + let idx = match component.defined_resource_index(resource.ty) { + Some(idx) => idx, + None => return false, + }; + resource.instance == component.defined_resource_instances[idx] } /// TODO pub fn resource_rep32(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result { - self.resource_tables.resource_rep(resource, idx) + self.resource_tables().resource_rep(Some(resource), idx) } /// TODO @@ -711,17 +679,37 @@ impl ComponentInstance { resource: TypeResourceTableIndex, idx: u32, ) -> Result> { - self.resource_tables.resource_drop(resource, idx) + self.resource_tables().resource_drop(Some(resource), idx) + } + + fn resource_tables(&mut self) -> ResourceTables<'_> { + ResourceTables { + host_table: None, + calls: unsafe { (&mut *self.store()).component_calls() }, + tables: Some(&mut self.component_resource_tables), + } } /// TODO - pub fn enter_call(&mut self) { - self.resource_tables.enter_call(); + pub fn component_resource_tables( + &mut self, + ) -> &mut PrimaryMap { + &mut self.component_resource_tables } /// TODO - pub fn exit_call(&mut self) -> Result<()> { - self.resource_tables.exit_call() + 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) } } @@ -980,6 +968,7 @@ impl VMOpaqueContext { #[allow(missing_docs)] #[repr(transparent)] +#[derive(Copy, Clone)] pub struct InstanceFlags(SendSyncPtr); #[allow(missing_docs)] diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index 9811cad8a0c9..c678f2671d8a 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -3,13 +3,19 @@ use std::mem; use wasmtime_environ::component::TypeResourceTableIndex; use wasmtime_environ::PrimaryMap; -pub struct ResourceTables { - tables: PrimaryMap, - calls: Vec, +/// TODO +pub struct ResourceTables<'a> { + /// TODO + pub tables: Option<&'a mut PrimaryMap>, + /// TODO + pub host_table: Option<&'a mut ResourceTable>, + /// TODO + pub calls: &'a mut CallContexts, } +/// TODO #[derive(Default)] -struct ResourceTable { +pub struct ResourceTable { next: u32, slots: Vec, } @@ -20,6 +26,12 @@ enum Slot { Borrow { rep: u32, scope: usize }, } +/// TODO +#[derive(Default)] +pub struct CallContexts { + scopes: Vec, +} + #[derive(Default)] struct CallContext { lenders: Vec, @@ -27,38 +39,31 @@ struct CallContext { } #[derive(Copy, Clone)] -pub struct Lender { - ty: TypeResourceTableIndex, +struct Lender { + ty: Option, idx: u32, } -impl ResourceTables { - pub fn new(amt: usize) -> ResourceTables { - let mut tables = PrimaryMap::with_capacity(amt); - for _ in 0..amt { - tables.push(ResourceTable::default()); - } - ResourceTables { - tables, - calls: Vec::new(), +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: TypeResourceTableIndex, rep: u32) -> u32 { - self.tables[ty].insert(Slot::Own { rep, lend_count: 0 }) + 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: TypeResourceTableIndex, idx: u32) -> Result { - match self.tables[ty].get_mut(idx)? { - Slot::Own { rep, .. } | Slot::Borrow { rep, .. } => Ok(*rep), - Slot::Free { .. } => unreachable!(), - } + 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 @@ -73,12 +78,16 @@ impl ResourceTables { /// 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: TypeResourceTableIndex, idx: u32) -> Result> { - match self.tables[ty].remove(idx)? { + 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[scope].borrow_count -= 1; + self.calls.scopes[scope].borrow_count -= 1; Ok(None) } Slot::Free { .. } => unreachable!(), @@ -94,8 +103,8 @@ impl ResourceTables { /// 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: TypeResourceTableIndex, rep: u32) -> u32 { - self.tables[ty].insert(Slot::Own { rep, lend_count: 0 }) + 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 @@ -105,8 +114,12 @@ impl ResourceTables { /// 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: TypeResourceTableIndex, idx: u32) -> Result { - match self.tables[ty].remove(idx)? { + 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"), @@ -123,17 +136,18 @@ impl ResourceTables { /// 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: TypeResourceTableIndex, idx: u32) -> Result { - match self.tables[ty].get_mut(idx)? { + 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(); - self.calls - .last_mut() - .unwrap() - .lenders - .push(Lender { ty, idx }); - Ok(*rep) + let rep = *rep; + self.register_lender(ty, idx); + Ok(rep) } Slot::Borrow { rep, .. } => Ok(*rep), Slot::Free { .. } => unreachable!(), @@ -152,19 +166,27 @@ impl ResourceTables { /// 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: TypeResourceTableIndex, rep: u32) -> u32 { - let scope = self.calls.len() - 1; - let borrow_count = &mut self.calls.last_mut().unwrap().borrow_count; + 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.tables[ty].insert(Slot::Borrow { rep, scope }) + self.table(ty).insert(Slot::Borrow { rep, scope }) } + /// TODO + fn register_lender(&mut self, ty: Option, idx: u32) { + let scope = self.calls.scopes.last_mut().unwrap(); + scope.lenders.push(Lender { ty, idx }); + } + + /// TODO pub fn enter_call(&mut self) { - self.calls.push(CallContext::default()); + self.calls.scopes.push(CallContext::default()); } + /// TODO pub fn exit_call(&mut self) -> Result<()> { - let cx = self.calls.pop().unwrap(); + let cx = self.calls.scopes.pop().unwrap(); if cx.borrow_count > 0 { bail!("borrow handles still remain at the end of the call") } @@ -172,7 +194,7 @@ impl ResourceTables { // 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.tables[lender.ty].get_mut(lender.idx).unwrap() { + match self.table(lender.ty).get_mut(lender.idx).unwrap() { Slot::Own { lend_count, .. } => { *lend_count -= 1; } @@ -203,6 +225,14 @@ impl ResourceTable { u32::try_from(ret).unwrap() } + /// TODO + pub 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() diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index e23851799348..bffe6cadc174 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; + + /// TODO + #[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/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 1e4d24254459..665086f9b2c1 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -6,7 +6,9 @@ use anyhow::{bail, Result}; use std::ptr::NonNull; use std::sync::Arc; use wasmtime_environ::component::{ComponentTypes, StringEncoding, TypeResourceTableIndex}; -use wasmtime_runtime::component::{ComponentInstance, InstanceFlags}; +use wasmtime_runtime::component::{ + CallContexts, ComponentInstance, InstanceFlags, ResourceTable, ResourceTables, +}; use wasmtime_runtime::{VMFuncRef, VMMemoryDefinition}; /// Runtime representation of canonical ABI options in the component model. @@ -267,15 +269,41 @@ impl<'a, T> LowerContext<'a, T> { } /// TODO - pub fn resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { - // TODO: document unsafe - unsafe { (*self.instance).resource_lower_own(ty, rep) } + pub fn guest_resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + self.resource_tables().resource_lower_own(Some(ty), rep) } /// TODO - pub fn resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { - // TODO: document unsafe - unsafe { (*self.instance).resource_lower_borrow(ty, rep) } + 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. + // + // TODO: unsafe + if unsafe { (*self.instance).resource_owned_by_own_instance(ty) } { + return rep; + } + self.resource_tables().resource_lower_borrow(Some(ty), rep) + } + + /// TODO + pub fn host_resource_lift_own(&mut self, idx: u32) -> Result { + self.resource_tables().resource_lift_own(None, idx) + } + + /// TODO + pub fn host_resource_lift_borrow(&mut self, idx: u32) -> Result { + self.resource_tables().resource_lift_borrow(None, idx) + } + + /// TODO + pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 { + self.resource_tables().resource_lower_own(None, rep) } /// TODO @@ -288,6 +316,16 @@ impl<'a, T> LowerContext<'a, T> { // TODO: document unsafe 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, + // TODO: document unsafe + tables: Some(unsafe { (*self.instance).component_resource_tables() }), + } + } } /// Contextual information used when lifting a type from a component into the @@ -306,6 +344,10 @@ pub struct LiftContext<'a> { memory: Option<&'a [u8]>, instance: *mut ComponentInstance, + + host_table: &'a mut ResourceTable, + + calls: &'a mut CallContexts, } #[doc(hidden)] @@ -321,11 +363,17 @@ impl<'a> LiftContext<'a> { types: &'a Arc, instance: *mut ComponentInstance, ) -> LiftContext<'a> { + // TODO: document `unsafe` + let (calls, host_table) = + unsafe { (&mut *(store as *mut StoreOpaque)).component_calls_and_host_table() }; + let memory = options.memory.map(|_| options.memory(store)); LiftContext { - memory: options.memory.map(|_| options.memory(store)), + memory, options, types, instance, + calls, + host_table, } } @@ -351,19 +399,33 @@ impl<'a> LiftContext<'a> { } /// TODO - pub fn resource_lift_own( + pub fn guest_resource_lift_own( &mut self, ty: TypeResourceTableIndex, idx: u32, ) -> Result<(u32, Option>, Option)> { - // TODO: document unsafe - unsafe { (*self.instance).resource_lift_own(ty, idx) } + let idx = self.resource_tables().resource_lift_own(Some(ty), idx)?; + let (dtor, flags) = unsafe { (*self.instance).dtor_and_flags(ty) }; + Ok((idx, dtor, flags)) } /// TODO - pub fn resource_lift_borrow(&mut self, ty: TypeResourceTableIndex, idx: u32) -> Result { - // TODO: document unsafe - unsafe { (*self.instance).resource_lift_borrow(ty, idx) } + pub fn guest_resource_lift_borrow( + &mut self, + ty: TypeResourceTableIndex, + idx: u32, + ) -> Result { + self.resource_tables().resource_lift_borrow(Some(ty), idx) + } + + /// TODO + pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 { + self.resource_tables().resource_lower_own(None, rep) + } + + /// TODO + pub fn host_resource_lower_borrow(&mut self, rep: u32) -> u32 { + self.resource_tables().resource_lower_borrow(None, rep) } /// TODO @@ -376,4 +438,13 @@ impl<'a> LiftContext<'a> { // TODO: document unsafe InstanceType::new(unsafe { &*self.instance }) } + + fn resource_tables(&mut self) -> ResourceTables<'_> { + ResourceTables { + host_table: Some(self.host_table), + calls: self.calls, + // TODO: document unsafe + tables: Some(unsafe { (*self.instance).component_resource_tables() }), + } + } } diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index d6e5bdfa76c4..21b9a3996de3 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -1,16 +1,15 @@ 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; -use crate::{AsContextMut, StoreContextMut, Trap}; +use crate::store::{StoreId, StoreOpaque}; +use crate::{AsContext, 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::{AtomicU64, Ordering::Relaxed}; use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType}; -use wasmtime_runtime::component::{ComponentInstance, InstanceFlags}; +use wasmtime_runtime::component::{ComponentInstance, InstanceFlags, ResourceTables}; use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw}; /// TODO @@ -55,47 +54,124 @@ enum ResourceTypeKind { } /// TODO +/// +/// document lack of dtor +/// +/// document it's both borrow and own pub struct Resource { - rep: ResourceRep, + repr: ResourceRepr, _marker: marker::PhantomData T>, } -impl Resource { +enum ResourceRepr { + Borrow(u32), + OwnInTable(u32), +} + +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, +{ /// TODO - pub fn new(rep: u32) -> Resource { + pub fn new(mut store: impl AsContextMut, rep: u32) -> Resource { + let store = store.as_context_mut().0; + let idx = host_resource_tables(store).resource_lower_own(None, rep); Resource { - rep: ResourceRep::new(rep), + repr: ResourceRepr::OwnInTable(idx), _marker: marker::PhantomData, } } - /// TODO - document panic - pub fn rep(&self) -> u32 { - match self.rep.get() { - Some(val) => val, - None => todo!(), + /// TODO + pub fn rep(&self, store: impl AsContext) -> Result { + match self.repr { + ResourceRepr::OwnInTable(idx) => store.as_context().0.host_table().rep(idx), + ResourceRepr::Borrow(rep) => Ok(rep), + } + } + + /// TODO + pub fn owned(&self) -> bool { + match self.repr { + ResourceRepr::OwnInTable(_) => true, + ResourceRepr::Borrow(_) => false, } } fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { - let resource = match ty { - InterfaceType::Own(t) => t, + match ty { + InterfaceType::Own(t) => { + let rep = match self.repr { + // 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. + ResourceRepr::OwnInTable(idx) => cx.host_resource_lift_own(idx)?, + + // If this is a borrow resource then this is a dynamic + // error on behalf of the embedder. + ResourceRepr::Borrow(_rep) => { + bail!("cannot lower a `borrow` resource into an `own`") + } + }; + Ok(cx.guest_resource_lower_own(t, rep)) + } + InterfaceType::Borrow(t) => { + let rep = match self.repr { + // Borrowing an owned resource may fail because it could + // have been previously moved out. If successful this + // operation will record that the resource is borrowed for + // the duration of this call. + ResourceRepr::OwnInTable(idx) => cx.host_resource_lift_borrow(idx)?, + + // Reborrowing host resources always succeeds and the + // representation can be plucked out easily here. + ResourceRepr::Borrow(rep) => rep, + }; + Ok(cx.guest_resource_lower_borrow(t, rep)) + } _ => bad_type_info(), - }; - let rep = self.rep.take()?; - Ok(cx.resource_lower_own(resource, rep)) + } } fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { - let resource = match ty { - InterfaceType::Own(t) => t, + let repr = match ty { + // Ownership is being transferred from a guest to the host, so move + // it from the guest table into a fresh slot in the host table. + 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()); + ResourceRepr::OwnInTable(cx.host_resource_lower_own(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)?; + ResourceRepr::Borrow(rep) + } _ => bad_type_info(), }; - let (rep, dtor, flags) = cx.resource_lift_own(resource, index)?; - debug_assert!(flags.is_none()); - debug_assert!(dtor.is_some()); - // TODO: should debug assert types match here - Ok(Resource::new(rep)) + Ok(Resource { + repr, + _marker: marker::PhantomData, + }) } } @@ -106,8 +182,8 @@ unsafe impl ComponentType for Resource { fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { let resource = match ty { - InterfaceType::Own(t) => *t, - other => bail!("expected `own` found `{}`", desc(other)), + 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 => {} @@ -153,12 +229,22 @@ unsafe impl Lift for Resource { } /// TODO +/// +/// document it's both borrow and own +/// +/// document dtor importance +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct ResourceAny { - store: StoreId, - rep: ResourceRep, + idx: u32, ty: ResourceType, - dtor: Option>, + own_state: Option, +} + +#[derive(Copy, Clone)] +struct OwnState { + store: StoreId, flags: Option, + dtor: Option>, } impl ResourceAny { @@ -167,6 +253,11 @@ impl ResourceAny { self.ty } + /// TODO + pub fn owned(&self) -> bool { + self.own_state.is_some() + } + /// TODO pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> { let mut store = store.as_context_mut(); @@ -193,11 +284,37 @@ impl ResourceAny { } fn resource_drop_impl(self, store: &mut StoreContextMut<'_, T>) -> Result<()> { - assert_eq!(store.0.id(), self.store); - let rep = self.rep.take()?; + // 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" + ); - // TODO - if let Some(flags) = &self.flags { + // 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); @@ -205,56 +322,67 @@ impl ResourceAny { } } - let dtor = match self.dtor { + let dtor = match state.dtor { Some(dtor) => dtor.as_non_null(), None => return Ok(()), }; let mut args = [ValRaw::u32(rep)]; - // TODO: unsafe call + + // 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 { - let resource = match ty { - InterfaceType::Own(t) => t, + 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(), - }; - if cx.resource_type(resource) != self.ty { - bail!("mismatched resource types") } - let rep = self.rep.take()?; - Ok(cx.resource_lower_own(resource, rep)) } fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { - let resource = match ty { - InterfaceType::Own(t) => t, + 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(), - }; - let (rep, dtor, flags) = cx.resource_lift_own(resource, index)?; - let ty = cx.resource_type(resource); - Ok(ResourceAny { - store: cx.store_id(), - rep: ResourceRep::new(rep), - ty, - dtor: dtor.map(SendSyncPtr::new), - flags, - }) - } - - /// TODO - pub(crate) fn partial_eq_for_val(&self, other: &ResourceAny) -> bool { - self.store == other.store && self.ty == other.ty && self.rep.get() == other.rep.get() - } - - /// TODO - pub(crate) fn clone_for_val(&self) -> ResourceAny { - ResourceAny { - store: self.store, - ty: self.ty, - rep: ResourceRep::empty(), - dtor: None, - flags: None, } } } @@ -266,8 +394,8 @@ unsafe impl ComponentType for ResourceAny { fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { - InterfaceType::Own(_) => Ok(()), - other => bail!("expected `own` found `{}`", desc(other)), + InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()), + other => bail!("expected `own` or `borrow`, found `{}`", desc(other)), } } } @@ -306,37 +434,22 @@ unsafe impl Lift for ResourceAny { } } -/// TODO -struct ResourceRep(AtomicU64); - -impl ResourceRep { - fn new(rep: u32) -> ResourceRep { - ResourceRep(AtomicU64::new((u64::from(rep) << 1) | 1)) - } - - fn empty() -> ResourceRep { - ResourceRep(AtomicU64::new(0)) - } - - fn take(&self) -> Result { - match self.0.swap(0, Relaxed) { - 0 => bail!("resource already consumed"), - n => Ok((n >> 1) as u32), - } - } - - fn get(&self) -> Option { - match self.0.load(Relaxed) { - 0 => None, - n => Some((n >> 1) as u32), - } +impl fmt::Debug for OwnState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OwnState") + .field("store", &self.store) + .finish() } } -impl fmt::Debug for ResourceAny { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ResourceAny") - .field("rep", &self.rep.get()) - .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/values.rs b/crates/wasmtime/src/component/values.rs index defb187a969b..f89660cc41b7 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -595,10 +595,8 @@ impl fmt::Debug for Flags { /// Represents possible runtime values which a component function can either /// consume or produce /// -/// TODO: fill in notes on Clone -/// /// TODO: fill in notes on PartialEq -#[derive(Debug)] +#[derive(Debug, Clone)] #[allow(missing_docs)] pub enum Val { Bool(bool), @@ -1140,7 +1138,7 @@ 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.partial_eq_for_val(r), + (Self::Resource(l), Self::Resource(r)) => l == r, (Self::Resource(_), _) => false, } } @@ -1148,36 +1146,6 @@ impl PartialEq for Val { impl Eq for Val {} -impl Clone for Val { - fn clone(&self) -> Val { - match self { - Self::Bool(v) => Self::Bool(*v), - Self::U8(v) => Self::U8(*v), - Self::S8(v) => Self::S8(*v), - Self::U16(v) => Self::U16(*v), - Self::S16(v) => Self::S16(*v), - Self::U32(v) => Self::U32(*v), - Self::S32(v) => Self::S32(*v), - Self::U64(v) => Self::U64(*v), - Self::S64(v) => Self::S64(*v), - Self::Float32(v) => Self::Float32(*v), - Self::Float64(v) => Self::Float64(*v), - Self::Char(v) => Self::Char(*v), - Self::String(s) => Self::String(s.clone()), - Self::List(s) => Self::List(s.clone()), - Self::Record(s) => Self::Record(s.clone()), - Self::Tuple(s) => Self::Tuple(s.clone()), - Self::Variant(s) => Self::Variant(s.clone()), - Self::Enum(s) => Self::Enum(s.clone()), - Self::Union(s) => Self::Union(s.clone()), - Self::Flags(s) => Self::Flags(s.clone()), - Self::Option(s) => Self::Option(s.clone()), - Self::Result(s) => Self::Result(s.clone()), - Self::Resource(s) => Self::Resource(s.clone_for_val()), - } - } -} - struct GenericVariant<'a> { discriminant: u32, payload: Option<(&'a Val, InterfaceType)>, diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index df3f6d0231f7..23e350d99dd6 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -338,6 +338,11 @@ 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>>, + + #[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 +479,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 +1545,21 @@ at https://bytecodealliance.org/security. ); std::process::abort(); } + + #[cfg(feature = "component-model")] + pub(crate) fn host_table(&self) -> &wasmtime_runtime::component::ResourceTable { + &self.component_host_table + } + + #[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 +2071,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/wast/src/spectest.rs b/crates/wast/src/spectest.rs index aa469100e6a5..2fc52e911ad6 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -106,13 +106,13 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( panic!("TODO: shouldn't have to specify dtor twice"); })?; - i.func_wrap("[constructor]resource1", |_, (rep,): (u32,)| { - Ok((Resource::::new(rep),)) + i.func_wrap("[constructor]resource1", |mut cx, (rep,): (u32,)| { + Ok((Resource::::new(&mut cx, rep),)) })?; i.func_wrap( "[static]resource1.assert", - |_, (resource, rep): (Resource, u32)| { - assert_eq!(resource.rep(), rep); + |cx, (resource, rep): (Resource, u32)| { + assert_eq!(resource.rep(&cx)?, rep); Ok(()) }, )?; diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 0177db1a47cf..5c6b54f97dfe 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -339,7 +339,7 @@ fn drop_guest_twice() -> Result<()> { assert_eq!( dtor.call(&mut store, (&t,)).unwrap_err().to_string(), - "resource already consumed" + "unknown handle index 0" ); Ok(()) @@ -369,13 +369,13 @@ fn drop_host_twice() -> Result<()> { let i = linker.instantiate(&mut store, &c)?; let dtor = i.get_typed_func::<(&Resource,), ()>(&mut store, "dtor")?; - let t = Resource::new(100); + let t = Resource::new(&mut store, 100); dtor.call(&mut store, (&t,))?; dtor.post_return(&mut store)?; assert_eq!( dtor.call(&mut store, (&t,)).unwrap_err().to_string(), - "resource already consumed" + "unknown handle index 0" ); Ok(()) @@ -441,7 +441,7 @@ fn manually_destroy() -> Result<()> { 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(100); + let t1 = Resource::::new(&mut store, 100); let (t1,) = t1_pass.call(&mut store, (t1,))?; t1_pass.post_return(&mut store)?; assert_eq!(store.data().drops, 0); @@ -553,7 +553,8 @@ fn dynamic_val() -> Result<()> { let b = i.get_func(&mut store, "b").unwrap(); let t2 = i.get_resource(&mut store, "t2").unwrap(); - let (t1,) = a_typed.call(&mut store, (Resource::new(100),))?; + let t1 = Resource::new(&mut store, 100); + let (t1,) = a_typed.call(&mut store, (t1,))?; a_typed.post_return(&mut store)?; assert_eq!(t1.ty(), ResourceType::host::()); From faa0f3a546714ad3078f9d8a79b458e0b5f47e91 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Jul 2023 11:00:04 -0700 Subject: [PATCH 29/47] Add a suite of tests for borrowing resources Code coverage was used to ensure that almost all of the various paths through the code are taken to ensure all the basic bases are covered. There's probably still lurking bugs, but this should be a solid enough base to start from hopefully. --- crates/environ/src/fact/trampoline.rs | 4 +- crates/wasmtime/src/component/func.rs | 18 +- crates/wasmtime/src/component/func/host.rs | 19 +- crates/wasmtime/src/component/func/options.rs | 20 + crates/wasmtime/src/component/resources.rs | 19 +- crates/wast/src/spectest.rs | 27 +- tests/all/component_model/resources.rs | 485 +++++++++++++++++- .../component-model/resources.wast | 152 ++++++ 8 files changed, 725 insertions(+), 19 deletions(-) diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index 40f056e66b07..176528dad159 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -2464,7 +2464,7 @@ impl Compiler<'_, '_> { _ => panic!("expected an `Own`"), }; - drop((src_ty, src, dst_ty, dst)); + let _ = (src_ty, src, dst_ty, dst); todo!("TODO: #6696"); } @@ -2480,7 +2480,7 @@ impl Compiler<'_, '_> { _ => panic!("expected an `Borrow`"), }; - drop((src_ty, src, dst_ty, dst)); + let _ = (src_ty, src, dst_ty, dst); todo!("TODO: #6696"); } diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 247d5de247b4..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` @@ -474,8 +475,10 @@ impl Func { 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::new(store.as_context_mut(), &options, &types, instance_ptr), + &mut cx, params, InterfaceType::Tuple(types[ty].params), map_maybe_uninit!(space.params), @@ -600,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 @@ -653,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(()) } diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs index 8277063d0735..a048275b9090 100644 --- a/crates/wasmtime/src/component/func/host.rs +++ b/crates/wasmtime/src/component/func/host.rs @@ -220,20 +220,18 @@ where Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref()) } }; - let params = storage.lift_params( - &mut LiftContext::new(cx.0, &options, types, instance), - 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::new(cx, &options, types, instance), - 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> { @@ -349,6 +347,7 @@ where let param_tys = &types[func_ty.params]; let result_tys = &types[func_ty.results]; 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 = @@ -405,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 665086f9b2c1..9797a1c9fe1f 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -326,6 +326,16 @@ impl<'a, T> LowerContext<'a, T> { tables: Some(unsafe { (*self.instance).component_resource_tables() }), } } + + /// TODO + pub fn enter_call(&mut self) { + self.resource_tables().enter_call() + } + + /// TODO + pub fn exit_call(&mut self) -> Result<()> { + self.resource_tables().exit_call() + } } /// Contextual information used when lifting a type from a component into the @@ -447,4 +457,14 @@ impl<'a> LiftContext<'a> { tables: Some(unsafe { (*self.instance).component_resource_tables() }), } } + + /// TODO + pub fn enter_call(&mut self) { + self.resource_tables().enter_call() + } + + /// TODO + pub fn exit_call(&mut self) -> Result<()> { + self.resource_tables().exit_call() + } } diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index 21b9a3996de3..3861db26e3aa 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -63,6 +63,7 @@ pub struct Resource { _marker: marker::PhantomData T>, } +#[derive(Debug)] enum ResourceRepr { Borrow(u32), OwnInTable(u32), @@ -82,7 +83,7 @@ where T: 'static, { /// TODO - pub fn new(mut store: impl AsContextMut, rep: u32) -> Resource { + pub fn new_own(mut store: impl AsContextMut, rep: u32) -> Resource { let store = store.as_context_mut().0; let idx = host_resource_tables(store).resource_lower_own(None, rep); Resource { @@ -91,6 +92,14 @@ where } } + /// TODO + pub fn new_borrow(rep: u32) -> Resource { + Resource { + repr: ResourceRepr::Borrow(rep), + _marker: marker::PhantomData, + } + } + /// TODO pub fn rep(&self, store: impl AsContext) -> Result { match self.repr { @@ -228,6 +237,14 @@ unsafe impl Lift for Resource { } } +impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("repr", &self.repr) + .finish() + } +} + /// TODO /// /// document it's both borrow and own diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 2fc52e911ad6..8ded3a877647 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -107,7 +107,7 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( })?; i.func_wrap("[constructor]resource1", |mut cx, (rep,): (u32,)| { - Ok((Resource::::new(&mut cx, rep),)) + Ok((Resource::::new_own(&mut cx, rep),)) })?; i.func_wrap( "[static]resource1.assert", @@ -124,5 +124,30 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( 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(&cx)?, 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/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 5c6b54f97dfe..605ff7578426 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -369,7 +369,7 @@ fn drop_host_twice() -> Result<()> { let i = linker.instantiate(&mut store, &c)?; let dtor = i.get_typed_func::<(&Resource,), ()>(&mut store, "dtor")?; - let t = Resource::new(&mut store, 100); + let t = Resource::new_own(&mut store, 100); dtor.call(&mut store, (&t,))?; dtor.post_return(&mut store)?; @@ -441,7 +441,7 @@ fn manually_destroy() -> Result<()> { 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(&mut store, 100); + let t1 = Resource::new_own(&mut store, 100); let (t1,) = t1_pass.call(&mut store, (t1,))?; t1_pass.post_return(&mut store)?; assert_eq!(store.data().drops, 0); @@ -553,7 +553,7 @@ fn dynamic_val() -> Result<()> { let b = i.get_func(&mut store, "b").unwrap(); let t2 = i.get_resource(&mut store, "t2").unwrap(); - let t1 = Resource::new(&mut store, 100); + let t1 = Resource::new_own(&mut store, 100); let (t1,) = a_typed.call(&mut store, (t1,))?; a_typed.post_return(&mut store)?; assert_eq!(t1.ty(), ResourceType::host::()); @@ -637,3 +637,482 @@ fn cannot_reenter_during_import() -> Result<()> { 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(&mut store, 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(&cx)?, 100); + Ok(()) + })?; + let i = linker.instantiate(&mut store, &c)?; + + let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; + + let resource = Resource::new_own(&mut store, 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(&mut store, 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(&mut store, 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(&mut store, 100); + let err = f.call(&mut store, (&resource, &resource)).unwrap_err(); + assert!( + format!("{err:?}").contains("unknown handle index 0"), + "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(&mut store, 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(&mut store, 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(()) +} + +// TODO: resource.drop on owned resource actively borrowed diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index ecfc70b6d08f..3d521beffa2a 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -804,3 +804,155 @@ )) )) ) + +;; 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") From 252fb2fa4e030c8fec2ecfa5f48a057aef7a5eac Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Jul 2023 11:21:08 -0700 Subject: [PATCH 30/47] Fill in an issue for bindgen todo --- crates/wit-bindgen/src/lib.rs | 4 ++-- crates/wit-bindgen/src/rust.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) 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!(), }, } From 2e1df2b687e7c68a615fa411939e401785f926d5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Jul 2023 13:58:29 -0700 Subject: [PATCH 31/47] Add docs, still more to go --- crates/cranelift/src/compiler.rs | 1 - crates/environ/src/component/info.rs | 51 ++--- crates/environ/src/component/translate.rs | 78 ++++++-- .../environ/src/component/translate/adapt.rs | 2 +- .../environ/src/component/translate/inline.rs | 164 +++++++++++++-- crates/environ/src/component/types.rs | 87 ++++++-- .../environ/src/component/types/resources.rs | 187 ++++++++++++++++-- .../src/component/vmcomponent_offsets.rs | 8 +- crates/environ/src/fact/trampoline.rs | 2 +- .../fuzzing/src/generators/component_types.rs | 5 +- crates/runtime/src/component.rs | 86 +++++--- crates/runtime/src/component/resources.rs | 102 ++++++++-- crates/runtime/src/component/transcode.rs | 8 +- crates/runtime/src/lib.rs | 2 +- crates/wasmtime/src/compiler.rs | 8 +- crates/wasmtime/src/component/component.rs | 21 +- crates/wasmtime/src/component/func/options.rs | 93 +++++---- 17 files changed, 713 insertions(+), 192 deletions(-) diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 01a28bda8c53..3f6aa8dffbd4 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -378,7 +378,6 @@ 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, diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 602761edec77..1179491a0a6f 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -402,11 +402,13 @@ pub enum CoreDef { /// host-defined transcoding function. Transcoder(RuntimeTranscoderIndex), - /// TODO + /// This refers to a `resource.new` intrinsic described by the index + /// provided. These indices are created through `GlobalInitializer` + /// entries. ResourceNew(RuntimeResourceNewIndex), - /// TODO + /// Same as `ResourceNew`, but for the `resource.rep` intrinsic ResourceRep(RuntimeResourceRepIndex), - /// TODO + /// Same as `ResourceNew`, but for the `resource.drop` intrinsic ResourceDrop(RuntimeResourceDropIndex), } @@ -578,70 +580,73 @@ impl Transcoder { pub use crate::fact::{FixedEncoding, Transcode}; -/// TODO +/// 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 { - /// TODO + /// The local index of the resource being defined. pub index: DefinedResourceIndex, - /// TODO + /// Core wasm representation of this resource. pub rep: WasmType, - /// TODO + /// Optionally-specified destructor and where it comes from. pub dtor: Option, - /// TODO + /// Which component instance this resource logically belongs to. pub instance: RuntimeComponentInstanceIndex, } -/// TODO +/// 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 { - /// TODO + /// The index of the intrinsic being created. pub index: RuntimeResourceNewIndex, - /// TODO + /// The resource table that this intrinsic will be modifying. pub resource: TypeResourceTableIndex, - /// TODO + /// The core wasm signature of the intrinsic, always `(func (param i32) + /// (result i32))`. pub signature: SignatureIndex, } impl ResourceNew { - /// TODO + /// 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}") } } -/// TODO +/// Same as `ResourceNew`, but for the `resource.rep` intrinsic. #[derive(Debug, Serialize, Deserialize)] +#[allow(missing_docs)] pub struct ResourceRep { - /// TODO pub index: RuntimeResourceRepIndex, - /// TODO pub resource: TypeResourceTableIndex, - /// TODO pub signature: SignatureIndex, } impl ResourceRep { - /// TODO + /// 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}") } } -/// TODO +/// Same as `ResourceNew`, but for the `resource.drop` intrinsic. #[derive(Debug, Serialize, Deserialize)] +#[allow(missing_docs)] pub struct ResourceDrop { - /// TODO pub index: RuntimeResourceDropIndex, - /// TODO pub resource: TypeResourceTableIndex, - /// TODO pub signature: SignatureIndex, } impl ResourceDrop { - /// TODO + /// 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 0cbfb6c2bea3..09c8ea12ff59 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -149,11 +149,19 @@ struct Translation<'data> { /// index into an index space of what's being exported. exports: IndexMap<&'data str, ComponentItem>, - /// TODO + /// 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, } -// TODO: comment about how this uses wasmparser type information +// 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 @@ -257,7 +265,7 @@ impl<'a, 'data> Translator<'a, 'data> { result: Translation::default(), tunables, validator, - types: PreInliningComponentTypes { types }, + types: PreInliningComponentTypes::new(types), parser: Parser::new(0), lexical_scopes: Vec::new(), static_components: Default::default(), @@ -331,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.types, + self.types.types_mut_for_inlining(), &self.result, &self.static_modules, &self.static_components, @@ -362,6 +370,7 @@ impl<'a, 'data> Translator<'a, 'data> { } Payload::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 @@ -396,8 +405,11 @@ impl<'a, 'data> Translator<'a, 'data> { let mut component_type_index = self.validator.types(0).unwrap().component_type_count(); self.validator.component_type_section(&s)?; - let types = self.validator.types(0).unwrap(); + // 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 } => { @@ -878,24 +890,54 @@ impl<'a, 'data> Translator<'a, 'data> { } } -struct PreInliningComponentTypes<'a> { - types: &'a mut ComponentTypesBuilder, +impl Translation<'_> { + fn types_ref(&self) -> wasmparser::types::TypesRef<'_> { + self.types.as_ref().unwrap().as_ref() + } } -impl PreInliningComponentTypes<'_> { - fn module_types_builder(&mut self) -> &mut ModuleTypesBuilder { - self.types.module_types_builder() +/// 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 TypeConvert for PreInliningComponentTypes<'_> { - fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType { - self.types.lookup_heap_type(index) + 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 Translation<'_> { - fn types_ref(&self) -> wasmparser::types::TypesRef<'_> { - self.types.as_ref().unwrap().as_ref() + 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 ac33b0ea95ab..b695e58c30dc 100644 --- a/crates/environ/src/component/translate/adapt.rs +++ b/crates/environ/src/component/translate/adapt.rs @@ -185,7 +185,7 @@ impl<'data> Translator<'_, 'data> { // the dfg metadata for each adapter. for (module_id, adapter_module) in state.adapter_modules.iter() { let mut module = - fact::Module::new(self.types.types, self.tunables.debug_adapter_modules); + 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()); diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 57b37a3d3fb2..8681cb2f7db9 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -84,7 +84,12 @@ pub(super) fn run( _ => continue, }; - // TODO: comment this + // 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, @@ -99,11 +104,13 @@ pub(super) fn run( }, ); + // 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 are not required to be specified by the host since - // it's just for type information within the component. Note that this - // doesn't cover resource imports, which are in fact significant. + // 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; } @@ -206,7 +213,13 @@ struct InlinerFrame<'a> { component_instances: PrimaryMap>, components: PrimaryMap>, - /// TODO + /// 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, } @@ -329,6 +342,20 @@ 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, types: &mut ComponentTypesBuilder, @@ -339,8 +366,6 @@ impl<'a> Inliner<'a> { // initializers are processed. Currently this is modeled as an infinite // loop which drives the top-most iterator of the `frames` stack // provided as an argument to this function. - // - // TODO: comments about resources_cache loop { let (frame, _) = frames.last_mut().unwrap(); types.resources_mut().set_current_instance(frame.instance); @@ -405,11 +430,21 @@ 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. - // - // TODO: update comment 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, .. } => { @@ -423,8 +458,20 @@ impl<'a> Inliner<'a> { return Ok(None); } }; - let mut path = Vec::new(); + // 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(), @@ -432,6 +479,9 @@ impl<'a> Inliner<'a> { &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()); } @@ -445,10 +495,10 @@ impl<'a> Inliner<'a> { func, options, canonical_abi, - lower_ty: a, + lower_ty, } => { let lower_ty = - types.convert_component_func_type(frame.translation.types_ref(), *a)?; + 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 @@ -559,6 +609,16 @@ impl<'a> Inliner<'a> { }); } + // 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, @@ -568,11 +628,24 @@ impl<'a> Inliner<'a> { 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)); @@ -1063,7 +1136,8 @@ impl<'a> InlinerFrame<'a> { }) } - /// TODO + /// 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) => { @@ -1079,13 +1153,17 @@ impl<'a> InlinerFrame<'a> { self.component_instances.push(i); } - // 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. + // In short, type definitions aren't tracked here. // - // TODO: update comment + // 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. ComponentItemDef::Type(_ty) => {} } } @@ -1104,7 +1182,31 @@ impl<'a> InlinerFrame<'a> { } } - // TODO: comment this method + /// 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>, @@ -1156,21 +1258,43 @@ impl<'a> ComponentItemDef<'a> { 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 resourc 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!(), diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index c7aa897eb13b..d42dbc2c304c 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -110,16 +110,42 @@ indices! { pub struct TypeResultIndex(u32); /// Index pointing to a list type in the component model. pub struct TypeListIndex(u32); - /// TODO + + /// Index pointing to a resource table within a component. /// - /// TODO: this is not a great name, it's more of a "unique ID" and there - /// should be a separate pass, probably the dfg pass, which keeps track of - /// which resources actually need tables. Only those lifted or lowered - /// actually need tables, not literally all resources 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); - /// TODO + + /// 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); - /// TODO + + /// 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); // ======================================================================== @@ -193,11 +219,14 @@ indices! { /// which reference linear memories defined within a component. pub struct RuntimeTranscoderIndex(u32); - /// TODO + /// 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); - /// TODO + /// Same as `RuntimeResourceNewIndex`, but for `resource.rep` pub struct RuntimeResourceDropIndex(u32); - /// TODO + /// Same as `RuntimeResourceNewIndex`, but for `resource.drop` pub struct RuntimeResourceRepIndex(u32); } @@ -281,7 +310,14 @@ impl ComponentTypes { } } - /// TODO + /// 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() @@ -398,17 +434,19 @@ impl ComponentTypesBuilder { &mut self.module_types } - /// TODO + /// 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() } - /// TODO + /// Returns a mutable reference to the underlying `ResourcesBuilder`. pub fn resources_mut(&mut self) -> &mut ResourcesBuilder { &mut self.resources } - /// TODO + /// 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) } @@ -607,8 +645,7 @@ impl ComponentTypesBuilder { Ok(ret) } - /// TODO - pub fn valtype( + fn valtype( &mut self, types: types::TypesRef<'_>, ty: &types::ComponentValType, @@ -766,7 +803,8 @@ impl ComponentTypesBuilder { Ok(self.add_list_type(TypeList { element })) } - /// TODO + /// Converts a wasmparser `id`, which must point to a resource, to its + /// corresponding `TypeResourceTableIndex`. pub fn resource_id( &mut self, types: types::TypesRef<'_>, @@ -935,7 +973,10 @@ pub enum TypeDef { Module(TypeModuleIndex), /// A core wasm function using only core wasm types. CoreFunc(SignatureIndex), - /// TODO + /// 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), } @@ -1507,12 +1548,16 @@ pub struct TypeResult { pub info: VariantInfo, } -/// TODO +/// Metadata about a resource table added to a component. #[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] pub struct TypeResourceTable { - /// TODO + /// 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, - /// TODO + + /// The component instance that contains this resource table. pub instance: RuntimeComponentInstanceIndex, } diff --git a/crates/environ/src/component/types/resources.rs b/crates/environ/src/component/types/resources.rs index cf13b7363cc6..df48660c2411 100644 --- a/crates/environ/src/component/types/resources.rs +++ b/crates/environ/src/component/types/resources.rs @@ -1,3 +1,72 @@ +//! 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 "nominal" 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 subtelty 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 the 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, @@ -5,17 +74,73 @@ use crate::component::{ use std::collections::HashMap; use wasmparser::types; -/// TODO -/// TODO: this is a very special cache +/// 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 reuqired 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 { - /// TODO + /// 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, @@ -33,7 +158,21 @@ impl ResourcesBuilder { }) } - /// TODO + /// 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<'_>, @@ -42,14 +181,11 @@ impl ResourcesBuilder { register: &mut dyn FnMut(&[&'a str]) -> ResourceIndex, ) { match ty { - 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(); - } - } + // 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, @@ -60,7 +196,20 @@ impl ResourcesBuilder { .or_insert_with(|| register(path)); } - // TODO: comment why not needed + // 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(_) @@ -68,10 +217,13 @@ impl ResourcesBuilder { } } - /// TODO - pub fn register_resource<'a>( + /// 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<'a>, + types: types::TypesRef<'_>, id: types::TypeId, ty: ResourceIndex, ) { @@ -80,7 +232,8 @@ impl ResourcesBuilder { assert!(prev.is_none()); } - /// TODO + /// 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 bbbe7b114840..11650c9c3b76 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -62,13 +62,13 @@ pub struct VMComponentOffsets

{ pub num_always_trap: u32, /// Number of transcoders needed for string conversion. pub num_transcoders: u32, - /// TODO + /// Number of `resource.new` intrinsics within a component. pub num_resource_new: u32, - /// TODO + /// Number of `resource.rep` intrinsics within a component. pub num_resource_rep: u32, - /// TODO + /// Number of `resource.drop` intrinsics within a component. pub num_resource_drop: u32, - /// TODO + /// Number of resources within a component which need destructors stored. pub num_resources: u32, // precalculated offsets of various member fields diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index 176528dad159..9b7fd6e1806f 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -555,7 +555,7 @@ impl Compiler<'_, '_> { // 2 cases to consider for each of these variants. InterfaceType::Option(_) | InterfaceType::Result(_) => 2, - // TODO - something nonzero, is 1 right? + // TODO(#6696) - something nonzero, is 1 right? InterfaceType::Own(_) | InterfaceType::Borrow(_) => 1, }; diff --git a/crates/fuzzing/src/generators/component_types.rs b/crates/fuzzing/src/generators/component_types.rs index 8d4d048a8fbe..36ea589a5b01 100644 --- a/crates/fuzzing/src/generators/component_types.rs +++ b/crates/fuzzing/src/generators/component_types.rs @@ -126,8 +126,9 @@ pub fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrar .collect::>>()?, ) .unwrap(), - Type::Own(_) => todo!(), - Type::Borrow(_) => todo!(), + + // Resources aren't fuzzed at this time. + Type::Own(_) | Type::Borrow(_) => unreachable!(), }) } diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index d178b8aab5de..25aaa5ea363b 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -49,11 +49,19 @@ pub struct ComponentInstance { /// Runtime type information about this component. runtime_info: Arc, - /// TODO + /// 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, - /// TODO: - /// TODO: Any is bad + /// 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 @@ -135,7 +143,14 @@ pub struct VMComponentContext { } impl ComponentInstance { - /// TODO + /// 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, @@ -460,7 +475,7 @@ impl ComponentInstance { } } - /// Same as `set_lowering` but for the transcoder functions. + /// Same as `set_lowering` but for the resource.new functions. pub fn set_resource_new( &mut self, idx: RuntimeResourceNewIndex, @@ -480,7 +495,7 @@ impl ComponentInstance { } } - /// Same as `set_lowering` but for the transcoder functions. + /// Same as `set_lowering` but for the resource.rep functions. pub fn set_resource_rep( &mut self, idx: RuntimeResourceRepIndex, @@ -500,7 +515,7 @@ impl ComponentInstance { } } - /// Same as `set_lowering` but for the transcoder functions. + /// Same as `set_lowering` but for the resource.drop functions. pub fn set_resource_drop( &mut self, idx: RuntimeResourceDropIndex, @@ -539,7 +554,10 @@ impl ComponentInstance { }; } - /// TODO + /// 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, @@ -552,7 +570,10 @@ impl ComponentInstance { } } - /// TODO + /// 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); @@ -637,7 +658,7 @@ impl ComponentInstance { } } - /// TODO + /// Returns a reference to the component type information for this instance. pub fn component(&self) -> &Component { self.runtime_info.component() } @@ -647,17 +668,18 @@ impl ComponentInstance { self.runtime_info.component_types() } - /// TODO + /// 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 } - /// TODO - pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> u32 { - self.resource_tables().resource_new(Some(resource), rep) - } - - /// TODO + /// 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(); @@ -668,12 +690,19 @@ impl ComponentInstance { resource.instance == component.defined_resource_instances[idx] } - /// TODO + /// 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) } - /// TODO + /// Implementation of the `resource.drop` intrinsic. pub fn resource_drop( &mut self, resource: TypeResourceTableIndex, @@ -682,6 +711,13 @@ impl ComponentInstance { 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, @@ -690,14 +726,18 @@ impl ComponentInstance { } } - /// TODO + /// Returns the runtime state of resources associated with this component. pub fn component_resource_tables( &mut self, ) -> &mut PrimaryMap { &mut self.component_resource_tables } - /// TODO + /// 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, @@ -779,7 +819,7 @@ impl OwnedComponentInstance { &mut *self.ptr.as_ptr() } - /// TODO + /// Returns the underlying component instance's raw pointer. pub fn instance_ptr(&self) -> *mut ComponentInstance { self.ptr.as_ptr() } @@ -924,7 +964,7 @@ impl OwnedComponentInstance { unsafe { self.instance_mut().set_resource_destructor(idx, dtor) } } - /// TODO + /// See `ComponentInstance::resource_types` pub fn resource_types_mut(&mut self) -> &mut Arc { unsafe { &mut (*self.ptr.as_ptr()).resource_types } } diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index c678f2671d8a..fd8caba45607 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -1,32 +1,102 @@ +//! 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; -/// TODO +/// 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> { - /// TODO + /// 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>, - /// TODO + + /// 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>, - /// TODO + + /// Scope information about calls actively in use to track information such + /// as borrow counts. pub calls: &'a mut CallContexts, } -/// TODO +/// 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 }, } -/// TODO +/// 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, @@ -146,7 +216,8 @@ impl ResourceTables<'_> { // The decrement to this count happens in `exit_call`. *lend_count = lend_count.checked_add(1).unwrap(); let rep = *rep; - self.register_lender(ty, idx); + let scope = self.calls.scopes.last_mut().unwrap(); + scope.lenders.push(Lender { ty, idx }); Ok(rep) } Slot::Borrow { rep, .. } => Ok(*rep), @@ -173,18 +244,17 @@ impl ResourceTables<'_> { self.table(ty).insert(Slot::Borrow { rep, scope }) } - /// TODO - fn register_lender(&mut self, ty: Option, idx: u32) { - let scope = self.calls.scopes.last_mut().unwrap(); - scope.lenders.push(Lender { ty, idx }); - } - - /// TODO + /// 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()); } - /// TODO + /// 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 { @@ -225,7 +295,7 @@ impl ResourceTable { u32::try_from(ret).unwrap() } - /// TODO + /// Gets the representation of the `idx` specified. pub 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}"), diff --git a/crates/runtime/src/component/transcode.rs b/crates/runtime/src/component/transcode.rs index c5ae4925c67d..7aebc5bc7e22 100644 --- a/crates/runtime/src/component/transcode.rs +++ b/crates/runtime/src/component/transcode.rs @@ -8,7 +8,7 @@ use wasmtime_environ::component::TypeResourceTableIndex; const UTF16_TAG: usize = 1 << 31; -#[repr(C)] +#[repr(C)] // this is read by Cranelift code so it's layout must be as-written pub struct VMComponentLibcalls { builtins: VMComponentBuiltins, transcoders: VMBuiltinTranscodeArray, @@ -35,7 +35,8 @@ macro_rules! signature { (@retptr $other:ident) => (()); } -/// TODO +/// Defines a `VMComponentBuiltins` structure which contains any builtins such +/// as resource-related intrinsics. macro_rules! define_builtins { ( $( @@ -60,9 +61,6 @@ macro_rules! define_builtins { }; } }; - - (@ty vmctx) => (*mut VMComponentContext); - (@ty u32) => (u32); } wasmtime_environ::foreach_builtin_component_function!(define_builtins); diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index bffe6cadc174..23896612af6e 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -153,7 +153,7 @@ pub unsafe trait Store { /// completely semantically transparent. Returns the new deadline. fn new_epoch(&mut self) -> Result; - /// TODO + /// Metadata required for resources for the component model. #[cfg(feature = "component-model")] fn component_calls(&mut self) -> &mut component::CallContexts; } diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index bd29f9ecceaa..6c2ac9984693 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -340,6 +340,12 @@ impl<'a> CompileInputs<'a> { } } + // If there are any resources defined within this component, the + // signature for `resource.drop` is mentioned somewhere, and the + // wasm-to-native trampoline for `resource.drop` hasn't been created yet + // then insert that here. This is possibly required by destruction of + // resources from the embedder and otherwise won't be explicitly + // requested through initializers above or such. if component.num_resources > 0 { if let Some(sig) = types.find_resource_drop_signature() { let key = CompileKey::wasm_to_native_trampoline(sig); @@ -580,7 +586,7 @@ pub struct FunctionIndices { // The index of each compiled function, bucketed by compile key kind. indices: BTreeMap>>, - // TODO + // If necessary the wasm-to-native trampoline required by `resource.drop` resource_drop_wasm_to_native_trampoline: Option, } diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index 35703f45f8b9..7665ac8614a1 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -76,13 +76,18 @@ struct CompiledComponentInfo { /// compiled image of this component. transcoders: PrimaryMap>, - /// TODO + /// Locations of cranelift-generated `resource.new` functions are located + /// within the component. resource_new: PrimaryMap>, - /// TODO + + /// Same as `resource_new`, but for `resource.rep` intrinsics. resource_rep: PrimaryMap>, - /// TODO + + /// 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, } @@ -389,11 +394,19 @@ impl Component { 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()); - // TODO + // 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 diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 9797a1c9fe1f..0ebf3387d29a 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -155,7 +155,7 @@ impl Options { self.string_encoding } - /// TODO + /// Returns the id of the store that this `Options` is connected to. pub fn store_id(&self) -> StoreId { self.store_id } @@ -188,17 +188,22 @@ pub struct LowerContext<'a, T> { /// lifting/lowering process. pub types: &'a ComponentTypes, - /// TODO + /// 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> { - /// TODO + /// Creates a new lowering context from the specified parameters. /// /// # Unsafety /// - /// TODO: ptr must be valid + /// 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, @@ -268,12 +273,16 @@ impl<'a, T> LowerContext<'a, T> { .unwrap() } - /// TODO + /// 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) } - /// TODO + /// 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 @@ -284,36 +293,36 @@ impl<'a, T> LowerContext<'a, T> { // against the owning instance of the resource that `ty` is working // with. // - // TODO: unsafe + // 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) } - /// TODO + /// 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) } - /// TODO + /// 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) } - /// TODO - pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 { - self.resource_tables().resource_lower_own(None, rep) - } - - /// TODO + /// Returns the underlying resource type for the `ty` table specified. pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType { self.instance_type().resource_type(ty) } - /// TODO + /// Returns the instance type information corresponding to the instance that + /// this context is lowering into. pub fn instance_type(&self) -> InstanceType<'_> { - // TODO: document unsafe + // Note that the unsafety here should be valid given the contract of + // `LowerContext::new`. InstanceType::new(unsafe { &*self.instance }) } @@ -322,17 +331,20 @@ impl<'a, T> LowerContext<'a, T> { ResourceTables { host_table: Some(host_table), calls, - // TODO: document unsafe + // Note that the unsafety here should be valid given the contract of + // `LowerContext::new`. tables: Some(unsafe { (*self.instance).component_resource_tables() }), } } - /// TODO + /// 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() } - /// TODO + /// 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() } @@ -362,11 +374,12 @@ pub struct LiftContext<'a> { #[doc(hidden)] impl<'a> LiftContext<'a> { - /// TODO + /// Creates a new lifting context given the provided context. /// /// # Unsafety /// - /// TODO: ptr must be valid + /// 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, @@ -398,28 +411,35 @@ impl<'a> LiftContext<'a> { self.memory.unwrap() } - /// TODO + /// Returns an identifier for the store from which this `LiftContext` was + /// created. pub fn store_id(&self) -> StoreId { self.options.store_id } - /// TODO + /// Returns the component instance raw pointer that is being lifted from. pub fn instance_ptr(&self) -> *mut ComponentInstance { self.instance } - /// TODO + /// 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)) } - /// TODO + /// Lifts a `borrow` resource from the guest at the `idx` specified. pub fn guest_resource_lift_borrow( &mut self, ty: TypeResourceTableIndex, @@ -428,24 +448,28 @@ impl<'a> LiftContext<'a> { self.resource_tables().resource_lift_borrow(Some(ty), idx) } - /// TODO + /// 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) } - /// TODO + /// 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) } - /// TODO + /// 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) } - /// TODO + /// Returns instance type information for the component instance that is + /// being lifted from. pub fn instance_type(&self) -> InstanceType<'_> { - // TODO: document unsafe + // Note that the unsafety here should be valid given the contract of + // `LiftContext::new`. InstanceType::new(unsafe { &*self.instance }) } @@ -453,17 +477,18 @@ impl<'a> LiftContext<'a> { ResourceTables { host_table: Some(self.host_table), calls: self.calls, - // TODO: document unsafe + // Note that the unsafety here should be valid given the contract of + // `LiftContext::new`. tables: Some(unsafe { (*self.instance).component_resource_tables() }), } } - /// TODO + /// Same as `LowerContext::enter_call` pub fn enter_call(&mut self) { self.resource_tables().enter_call() } - /// TODO + /// Same as `LiftContext::enter_call` pub fn exit_call(&mut self) -> Result<()> { self.resource_tables().exit_call() } From 45d7c64ee0279226fe499070722f196946f49544 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 14 Jul 2023 08:35:15 -0700 Subject: [PATCH 32/47] Fill out more documentation --- crates/wasmtime/src/component/func/options.rs | 10 ++- crates/wasmtime/src/component/func/typed.rs | 9 ++- crates/wasmtime/src/component/instance.rs | 26 +++++-- crates/wasmtime/src/component/linker.rs | 20 ++++- crates/wasmtime/src/component/matching.rs | 75 ++++++++++--------- crates/wasmtime/src/component/types.rs | 18 +++++ crates/wasmtime/src/component/values.rs | 36 ++++++++- crates/wasmtime/src/store.rs | 3 + crates/wast/src/spectest.rs | 7 +- 9 files changed, 154 insertions(+), 50 deletions(-) diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 0ebf3387d29a..56bf4730eb4e 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -386,10 +386,16 @@ impl<'a> LiftContext<'a> { types: &'a Arc, instance: *mut ComponentInstance, ) -> LiftContext<'a> { - // TODO: document `unsafe` + // 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) = - unsafe { (&mut *(store as *mut StoreOpaque)).component_calls_and_host_table() }; + (&mut *(store as *mut StoreOpaque)).component_calls_and_host_table(); let memory = options.memory.map(|_| options.memory(store)); + LiftContext { memory, options, diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs index cb5a64ec3985..1295994daad7 100644 --- a/crates/wasmtime/src/component/func/typed.rs +++ b/crates/wasmtime/src/component/func/typed.rs @@ -1391,7 +1391,12 @@ impl WasmList { 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()); - // TODO: comment unsafety, validity of `self.instance` carried over + // 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) @@ -1421,7 +1426,7 @@ impl WasmList { ) -> impl ExactSizeIterator> + 'a { let store = store.into().0; self.options.store_id().assert_belongs_to(store.id()); - // TODO: comment unsafety, validity of `self.instance` carried over + // 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()) diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index adf0f059bd22..eaf6065cc41d 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -122,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 @@ -135,7 +135,15 @@ impl Instance { .cloned() } - /// TODO + /// 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) } @@ -231,8 +239,12 @@ impl InstanceData { InstanceType::new(self.instance()) } - // TODO: NB: only during creation - fn resource_types_mut(&mut self) -> &mut PrimaryMap { + // 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() @@ -302,6 +314,10 @@ 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 { @@ -311,9 +327,9 @@ impl<'a> Instantiator<'a> { }; let i = self.data.resource_types_mut().push(ty); assert_eq!(i, idx); - // TODO: this should be `Some` self.data.state.set_resource_destructor(idx, Some(func_ref)); } + for initializer in env_component.initializers.iter() { match initializer { GlobalInitializer::InstantiateModule(m) => { diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs index a6e9689b641e..109efaa6a943 100644 --- a/crates/wasmtime/src/component/linker.rs +++ b/crates/wasmtime/src/component/linker.rs @@ -377,7 +377,25 @@ impl LinkerInstance<'_, T> { self.insert(name, Definition::Module(module.clone())) } - /// TODO + /// 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, diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs index 83f3348a778b..c68a4995d4b4 100644 --- a/crates/wasmtime/src/component/matching.rs +++ b/crates/wasmtime/src/component/matching.rs @@ -52,47 +52,50 @@ impl TypeChecker<'_> { TypeDef::Resource(i) => { let i = self.types[i].ty; - match actual { - Some(Definition::Resource(actual, _dtor)) => { - 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. - None => { - // TODO comment unwrap/get_mut - 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(()) - } + 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 alredy 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() => Ok(()), + // 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 diff --git a/crates/wasmtime/src/component/types.rs b/crates/wasmtime/src/component/types.rs index 7ddfc1e21c38..23d08ba1b600 100644 --- a/crates/wasmtime/src/component/types.rs +++ b/crates/wasmtime/src/component/types.rs @@ -16,6 +16,24 @@ 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, diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index f89660cc41b7..d776831db6eb 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -595,7 +595,41 @@ impl fmt::Debug for Flags { /// Represents possible runtime values which a component function can either /// consume or produce /// -/// TODO: fill in notes on PartialEq +/// 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 if they contain +/// the same value). +/// +/// 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 { diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 23e350d99dd6..25c7846241cd 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -339,6 +339,9 @@ pub struct StoreOpaque { /// `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")] diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 8ded3a877647..9f23d668e08a 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -92,8 +92,6 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( let state = Arc::new(ResourceState::default()); - // TODO: this is specifying two different destructors for `Resource1`, that - // seems bad. i.resource::("resource1", { let state = state.clone(); move |_, rep| { @@ -102,8 +100,11 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( } })?; 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!("TODO: shouldn't have to specify dtor twice"); + panic!("shouldn't be destroyed"); })?; i.func_wrap("[constructor]resource1", |mut cx, (rep,): (u32,)| { From 451cf0ad88a18186466a9e65a4ebfcce94e52656 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 14 Jul 2023 08:46:21 -0700 Subject: [PATCH 33/47] Fill out a test TODO --- tests/all/component_model/resources.rs | 84 +++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 605ff7578426..8baa2669df70 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -1115,4 +1115,86 @@ fn pass_host_borrow_to_guest() -> Result<()> { Ok(()) } -// TODO: resource.drop on owned resource actively borrowed +#[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", |mut cx, ()| { + Ok((Resource::::new_own(&mut cx, 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(()) +} From 8b60719a77342929e3494012df9083e7a703334f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 14 Jul 2023 09:39:50 -0700 Subject: [PATCH 34/47] Update the host `Resource` type * Add docs everywhere * Don't require a `Store` for creating the resource or getting the representation. The latter point is the main refactoring in this commit. This is done in preparation for `bindgen!` to use this type where host bindings generally do not have access to the store. --- crates/runtime/src/component/resources.rs | 3 +- crates/wasmtime/src/component/func/options.rs | 9 + crates/wasmtime/src/component/resources.rs | 313 +++++++++++++++--- crates/wasmtime/src/store.rs | 5 - crates/wast/src/spectest.rs | 12 +- tests/all/component_model/resources.rs | 32 +- 6 files changed, 291 insertions(+), 83 deletions(-) diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index fd8caba45607..378fe1e24926 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -295,8 +295,7 @@ impl ResourceTable { u32::try_from(ret).unwrap() } - /// Gets the representation of the `idx` specified. - pub fn rep(&self, idx: u32) -> Result { + 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), diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 56bf4730eb4e..08104dcb9d18 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -313,6 +313,15 @@ impl<'a, T> LowerContext<'a, T> { 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) diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index 3861db26e3aa..9656bacd1b27 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -2,24 +2,50 @@ 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::{AsContext, AsContextMut, StoreContextMut, Trap}; +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}; -/// TODO +/// 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 { - /// TODO + /// 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::()), @@ -53,22 +79,158 @@ enum ResourceTypeKind { }, } -/// TODO +/// A host-defined resource in the component model. /// -/// document lack of dtor +/// 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 no 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. /// -/// document it's both borrow and own +/// 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. +/// +/// # 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 { - repr: ResourceRepr, + /// 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>, -} -#[derive(Debug)] -enum ResourceRepr { - Borrow(u32), - OwnInTable(u32), + /// 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 { @@ -82,68 +244,109 @@ impl Resource where T: 'static, { - /// TODO - pub fn new_own(mut store: impl AsContextMut, rep: u32) -> Resource { - let store = store.as_context_mut().0; - let idx = host_resource_tables(store).resource_lower_own(None, rep); + /// 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 { - repr: ResourceRepr::OwnInTable(idx), + state: AtomicU32::new(NOT_IN_TABLE), + rep, _marker: marker::PhantomData, } } - /// TODO + /// 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 { - repr: ResourceRepr::Borrow(rep), + state: AtomicU32::new(BORROW), + rep, _marker: marker::PhantomData, } } - /// TODO - pub fn rep(&self, store: impl AsContext) -> Result { - match self.repr { - ResourceRepr::OwnInTable(idx) => store.as_context().0.host_table().rep(idx), - ResourceRepr::Borrow(rep) => Ok(rep), - } + /// Returns the underlying 32-bit representation used to originally create + /// this resource. + pub fn rep(&self) -> u32 { + self.rep } - /// TODO + /// 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.repr { - ResourceRepr::OwnInTable(_) => true, - ResourceRepr::Borrow(_) => false, + 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.repr { - // 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. - ResourceRepr::OwnInTable(idx) => cx.host_resource_lift_own(idx)?, - + let rep = match self.state.load(Relaxed) { // If this is a borrow resource then this is a dynamic // error on behalf of the embedder. - ResourceRepr::Borrow(_rep) => { + 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.repr { - // Borrowing an owned resource may fail because it could - // have been previously moved out. If successful this - // operation will record that the resource is borrowed for - // the duration of this call. - ResourceRepr::OwnInTable(idx) => cx.host_resource_lift_borrow(idx)?, - - // Reborrowing host resources always succeeds and the - // representation can be plucked out easily here. - ResourceRepr::Borrow(rep) => rep, + 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)) } @@ -152,15 +355,18 @@ where } fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result { - let repr = match ty { + let (state, rep) = match ty { // Ownership is being transferred from a guest to the host, so move - // it from the guest table into a fresh slot in the host table. + // 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()); - ResourceRepr::OwnInTable(cx.host_resource_lower_own(rep)) + (AtomicU32::new(NOT_IN_TABLE), rep) } // The borrow here is lifted from the guest, but note the lack of @@ -173,12 +379,13 @@ where InterfaceType::Borrow(t) => { debug_assert!(cx.resource_type(t) == ResourceType::host::()); let rep = cx.guest_resource_lift_borrow(t, index)?; - ResourceRepr::Borrow(rep) + (AtomicU32::new(BORROW), rep) } _ => bad_type_info(), }; Ok(Resource { - repr, + state, + rep, _marker: marker::PhantomData, }) } @@ -239,9 +446,7 @@ unsafe impl Lift for Resource { impl fmt::Debug for Resource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Resource") - .field("repr", &self.repr) - .finish() + f.debug_struct("Resource").field("rep", &self.rep).finish() } } diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 25c7846241cd..cbd124e8fdae 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -1549,11 +1549,6 @@ at https://bytecodealliance.org/security. std::process::abort(); } - #[cfg(feature = "component-model")] - pub(crate) fn host_table(&self) -> &wasmtime_runtime::component::ResourceTable { - &self.component_host_table - } - #[cfg(feature = "component-model")] pub(crate) fn component_calls_and_host_table( &mut self, diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 9f23d668e08a..6e340aef2daf 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -107,13 +107,13 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( panic!("shouldn't be destroyed"); })?; - i.func_wrap("[constructor]resource1", |mut cx, (rep,): (u32,)| { - Ok((Resource::::new_own(&mut cx, rep),)) + 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(&cx)?, rep); + |_cx, (resource, rep): (Resource, u32)| { + assert_eq!(resource.rep(), rep); Ok(()) }, )?; @@ -127,9 +127,9 @@ pub fn link_component_spectest(linker: &mut component::Linker) -> Result<( })?; i.func_wrap( "[method]resource1.simple", - |cx, (resource, rep): (Resource, u32)| { + |_cx, (resource, rep): (Resource, u32)| { assert!(!resource.owned()); - assert_eq!(resource.rep(&cx)?, rep); + assert_eq!(resource.rep(), rep); Ok(()) }, )?; diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 8baa2669df70..7da1767e4711 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -369,13 +369,13 @@ fn drop_host_twice() -> Result<()> { let i = linker.instantiate(&mut store, &c)?; let dtor = i.get_typed_func::<(&Resource,), ()>(&mut store, "dtor")?; - let t = Resource::new_own(&mut store, 100); + 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(), - "unknown handle index 0" + "host resource already consumed" ); Ok(()) @@ -441,7 +441,7 @@ fn manually_destroy() -> Result<()> { 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(&mut store, 100); + 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); @@ -553,7 +553,7 @@ fn dynamic_val() -> Result<()> { let b = i.get_func(&mut store, "b").unwrap(); let t2 = i.get_resource(&mut store, "t2").unwrap(); - let t1 = Resource::new_own(&mut store, 100); + 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::()); @@ -667,7 +667,7 @@ fn active_borrows_at_end_of_call() -> Result<()> { let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f")?; - let resource = Resource::new_own(&mut store, 1); + let resource = Resource::new_own(1); f.call(&mut store, (&resource,))?; let err = f.post_return(&mut store).unwrap_err(); assert_eq!( @@ -720,16 +720,16 @@ fn thread_through_borrow() -> Result<()> { linker.root().resource::("t", |_, _| {})?; linker .root() - .func_wrap("f", |cx, (r,): (Resource,)| { + .func_wrap("f", |_cx, (r,): (Resource,)| { assert!(!r.owned()); - assert_eq!(r.rep(&cx)?, 100); + 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(&mut store, 100); + let resource = Resource::new_own(100); f.call(&mut store, (&resource,))?; f.post_return(&mut store)?; Ok(()) @@ -766,7 +766,7 @@ fn cannot_use_borrow_for_own() -> Result<()> { let f = i.get_typed_func::<(&Resource,), (Resource,)>(&mut store, "f")?; - let resource = Resource::new_own(&mut store, 100); + 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(()) @@ -814,7 +814,7 @@ fn passthrough_wrong_type() -> Result<()> { let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; - let resource = Resource::new_own(&mut store, 100); + 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`"), @@ -851,10 +851,10 @@ fn pass_moved_resource() -> Result<()> { let f = i.get_typed_func::<(&Resource, &Resource), ()>(&mut store, "f")?; - let resource = Resource::new_own(&mut store, 100); + let resource = Resource::new_own(100); let err = f.call(&mut store, (&resource, &resource)).unwrap_err(); assert!( - format!("{err:?}").contains("unknown handle index 0"), + format!("{err:?}").contains("host resource already consumed"), "bad error: {err:?}" ); Ok(()) @@ -988,7 +988,7 @@ fn host_borrow_as_resource_any() -> Result<()> { let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; - let resource = Resource::new_own(&mut store, 100); + let resource = Resource::new_own(100); f.call(&mut store, (&resource,))?; } @@ -1004,7 +1004,7 @@ fn host_borrow_as_resource_any() -> Result<()> { let f = i.get_typed_func::<(&Resource,), ()>(&mut store, "f2")?; - let resource = Resource::new_own(&mut store, 100); + 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"), @@ -1178,8 +1178,8 @@ fn drop_on_owned_resource() -> Result<()> { let mut store = Store::new(&engine, ()); let mut linker = Linker::new(&engine); linker.root().resource::("t", |_, _| {})?; - linker.root().func_wrap("[constructor]t", |mut cx, ()| { - Ok((Resource::::new_own(&mut cx, 300),)) + linker.root().func_wrap("[constructor]t", |_cx, ()| { + Ok((Resource::::new_own(300),)) })?; linker .root() From 89cbf53c44d43cabe220706899f069ac4918b41a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 14 Jul 2023 09:49:57 -0700 Subject: [PATCH 35/47] Document `ResourceAny` --- crates/wasmtime/src/component/resources.rs | 55 ++++++++++++++++++---- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index 9656bacd1b27..e64b71ff1a15 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -60,7 +60,6 @@ impl ResourceType { ResourceType { kind: ResourceTypeKind::Guest { store, - // TODO: comment this instance: instance as *const _ as usize, id, }, @@ -73,7 +72,9 @@ enum ResourceTypeKind { Host(TypeId), Guest { store: StoreId, - // TODO: comment what this `usize` is + // 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, }, @@ -107,6 +108,10 @@ enum ResourceTypeKind { /// 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, @@ -450,11 +455,28 @@ impl fmt::Debug for Resource { } } -/// TODO +/// 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. /// -/// document it's both borrow and own +/// 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. /// -/// document dtor importance +/// 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, @@ -470,17 +492,31 @@ struct OwnState { } impl ResourceAny { - /// TODO + /// 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 } - /// TODO + /// 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() } - /// TODO + /// 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!( @@ -490,7 +526,8 @@ impl ResourceAny { self.resource_drop_impl(&mut store.as_context_mut()) } - /// TODO + /// Same as [`ResourceAny::resource_drop`] except for use with async stores + /// to execute the destructor asynchronously. pub async fn resource_drop_async(self, mut store: impl AsContextMut) -> Result<()> where T: Send, From 1f9727937508fb48a767507c3d176b6c417a674b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 11:43:38 -0700 Subject: [PATCH 36/47] Debug assert dtor is non-null --- crates/cranelift/src/compiler/component.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 8cb9cbb513c4..cfe1ec948442 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -459,6 +459,12 @@ impl Compiler { 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, From 3820ceef72832c0e30d285c47b6a337a782763b0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 11:45:43 -0700 Subject: [PATCH 37/47] Review comments on loading libcalls --- crates/cranelift/src/compiler/component.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index cfe1ec948442..053c23b8abfd 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -522,6 +522,10 @@ 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<'_>, @@ -530,17 +534,18 @@ impl Compiler { offset: u32, ) -> ir::Value { let pointer_type = self.isa.pointer_type(); - // Load the host function pointer for this transcode which comes from a - // function pointer within the VMComponentContext's libcall array. + // First load the pointer to the libcalls structure which is static + // per-process. let libcalls_array = builder.ins().load( pointer_type, - MemFlags::trusted(), + 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(), + MemFlags::trusted().with_readonly(), libcalls_array, i32::try_from(offset * u32::from(offsets.ptr.size())).unwrap(), ) From 8190672326e673fa85b9c24dbcaf67c61307f8db Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 11:47:37 -0700 Subject: [PATCH 38/47] Update some comments --- crates/cranelift/src/compiler/component.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 053c23b8abfd..6b9702d26547 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -560,10 +560,13 @@ impl Compiler { ) -> 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 => { - // After the host function has returned the results are loaded from - // `values_vec_ptr` and then returned. let results = self.load_values_from_array( ty.params(), builder, @@ -586,12 +589,15 @@ impl Compiler { 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 => { - // After the host function has returned the results are loaded from - // `values_vec_ptr` and then returned. let block0_params = builder.func.dfg.block_params(block0); self.store_values_to_array( builder, From 9c88a0c62f5e33741d3a712e1bed367a7f986bae Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 11:49:07 -0700 Subject: [PATCH 39/47] Update a comment --- crates/environ/src/component/translate/inline.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 8681cb2f7db9..cf18a18c826f 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -1164,6 +1164,10 @@ impl<'a> InlinerFrame<'a> { // 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) => {} } } From fec8d273f23dd106ff651fdb36d58378f4e62902 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 11:52:44 -0700 Subject: [PATCH 40/47] Fix some typos --- .../environ/src/component/translate/inline.rs | 2 +- .../environ/src/component/types/resources.rs | 20 +++++++++---------- crates/runtime/src/component.rs | 6 +++--- crates/wasmtime/src/compiler.rs | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index cf18a18c826f..c0b68b345537 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -1268,7 +1268,7 @@ impl<'a> ComponentItemDef<'a> { /// 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 resourc itself. + /// definition which should refer to a resource itself. fn lookup_resource(&self, path: &[&str], types: &ComponentTypes) -> ResourceIndex { let mut cur = self.clone(); diff --git a/crates/environ/src/component/types/resources.rs b/crates/environ/src/component/types/resources.rs index df48660c2411..22a99d62fa8c 100644 --- a/crates/environ/src/component/types/resources.rs +++ b/crates/environ/src/component/types/resources.rs @@ -1,7 +1,7 @@ //! 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 "nominal" where two +//! 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. @@ -27,30 +27,30 @@ //! information wrong can compromise on all of these guarantees which is //! something Wasmtime would ideally avoid. //! -//! ## Interaction with wasmparser +//! ## 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 +//! 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 +//! `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 subtelty here the goal is to lean on `wasmparser` as much as +//! 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 +//! `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 +//! 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 the on purely talk about table indices. Each 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 @@ -114,7 +114,7 @@ pub struct ResourcesBuilder { /// A cache of the origin resource type behind a `ResourceId`. /// - /// Unlike `resource_id_to_table_index` this is reuqired to be eagerly + /// 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 @@ -137,7 +137,7 @@ impl ResourcesBuilder { /// allocated which produces a fresh `TypeResourceTableIndex` within the /// `types` provided. /// - /// Due to wasmparser's uniqueness of resource IDs combined with the + /// 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. diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index 25aaa5ea363b..50b836464965 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -335,17 +335,17 @@ impl ComponentInstance { unsafe { self.func_ref(self.offsets.transcoder_func_ref(idx)) } } - /// Same as `lowering_func_ref` except for the transcoding functions. + /// 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 transcoding functions. + /// 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 transcoding functions. + /// 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)) } } diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index 6c2ac9984693..8d66fe34bef6 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -341,7 +341,7 @@ impl<'a> CompileInputs<'a> { } // If there are any resources defined within this component, the - // signature for `resource.drop` is mentioned somewhere, and the + // signature for `resource.drop` is mentioned somewhere, and if the // wasm-to-native trampoline for `resource.drop` hasn't been created yet // then insert that here. This is possibly required by destruction of // resources from the embedder and otherwise won't be explicitly From 8242c0bb6172d0bcffec761a2e27de6e1a18ebf4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 12:00:24 -0700 Subject: [PATCH 41/47] Add a test that host types are the same when guest types differ --- tests/all/component_model/resources.rs | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 7da1767e4711..5b3a0a5110ac 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -1198,3 +1198,85 @@ fn drop_on_owned_resource() -> Result<()> { 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(()) +} From 0d95c57c94134f790ffc55e7a2205e493da42ba2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 12:02:22 -0700 Subject: [PATCH 42/47] Fix some typos --- crates/wasmtime/src/component/resources.rs | 2 +- crates/wasmtime/src/component/values.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index e64b71ff1a15..ea41df21cd13 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -84,7 +84,7 @@ enum ResourceTypeKind { /// /// 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 no forgeable by guests and +/// 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 diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs index d776831db6eb..f40a8f473947 100644 --- a/crates/wasmtime/src/component/values.rs +++ b/crates/wasmtime/src/component/values.rs @@ -622,8 +622,9 @@ impl fmt::Debug for Flags { /// 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 if they contain -/// the same value). +/// 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 From 491d1056aaaff77bc3fc537f843bf8d23c875c71 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 12:43:46 -0700 Subject: [PATCH 43/47] Thread things through a bit less --- crates/wasmtime/src/compiler.rs | 76 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index 8d66fe34bef6..b4704470437d 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -110,6 +110,7 @@ impl CompileKey { 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 { @@ -152,6 +153,13 @@ impl CompileKey { 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)] @@ -198,7 +206,6 @@ struct CompileOutput { pub struct CompileInputs<'a> { inputs: Vec<(CompileKey, CompileInput<'a>)>, input_keys: HashSet, - resource_drop_wasm_to_native_trampoline: Option, } impl<'a> CompileInputs<'a> { @@ -340,27 +347,30 @@ impl<'a> CompileInputs<'a> { } } - // If there are any resources defined within this component, the - // signature for `resource.drop` is mentioned somewhere, and if the - // wasm-to-native trampoline for `resource.drop` hasn't been created yet - // then insert that here. This is possibly required by destruction of - // resources from the embedder and otherwise won't be explicitly - // requested through initializers above or such. + // 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() { - let key = CompileKey::wasm_to_native_trampoline(sig); - ret.resource_drop_wasm_to_native_trampoline = Some(key); - if !ret.input_keys.contains(&key) { - ret.push_input(key, move |key, _tunables, compiler| { - let trampoline = compiler.compile_wasm_to_native_trampoline(&types[sig])?; - Ok(CompileOutput { - key, - symbol: "resource_drop_trampoline".to_string(), - function: CompiledFunction::Function(trampoline), - info: None, - }) - }); - } + let key = CompileKey::resource_drop_wasm_to_native_trampoline(); + ret.push_input(key, move |key, _tunables, compiler| { + let trampoline = compiler.compile_wasm_to_native_trampoline(&types[sig])?; + Ok(CompileOutput { + key, + symbol: "resource_drop_trampoline".to_string(), + function: CompiledFunction::Function(trampoline), + info: None, + }) + }); } } @@ -501,10 +511,7 @@ impl<'a> CompileInputs<'a> { .values() .all(|funcs| is_sorted_by_key(funcs, |x| x.key))); - Ok(UnlinkedCompileOutputs { - outputs, - resource_drop_wasm_to_native_trampoline: self.resource_drop_wasm_to_native_trampoline, - }) + Ok(UnlinkedCompileOutputs { outputs }) } } @@ -512,8 +519,6 @@ impl<'a> CompileInputs<'a> { pub struct UnlinkedCompileOutputs { // A map from kind to `CompileOutput`. outputs: BTreeMap>, - - resource_drop_wasm_to_native_trampoline: Option, } impl UnlinkedCompileOutputs { @@ -568,8 +573,6 @@ impl UnlinkedCompileOutputs { .or_default() .insert(x.key, index); } - indices.resource_drop_wasm_to_native_trampoline = - self.resource_drop_wasm_to_native_trampoline; (compiled_funcs, indices) } } @@ -585,9 +588,6 @@ pub struct FunctionIndices { // The index of each compiled function, bucketed by compile key kind. indices: BTreeMap>>, - - // If necessary the wasm-to-native trampoline required by `resource.drop` - resource_drop_wasm_to_native_trampoline: Option, } impl FunctionIndices { @@ -805,11 +805,15 @@ impl FunctionIndices { .into_iter() .map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1)) .collect(); - artifacts.resource_drop_wasm_to_native_trampoline = - self.resource_drop_wasm_to_native_trampoline.map(|i| { - let func = wasm_to_native_trampolines[&i].unwrap_function(); - symbol_ids_and_locs[func].1 - }); + 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!( From fa65029f42628c933324ccac938e4227673f4d1a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 12:46:41 -0700 Subject: [PATCH 44/47] Undo CompileKey-related changes --- crates/wasmtime/src/compiler.rs | 71 +++++++++++++-------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/crates/wasmtime/src/compiler.rs b/crates/wasmtime/src/compiler.rs index b4704470437d..24fadd4cc996 100644 --- a/crates/wasmtime/src/compiler.rs +++ b/crates/wasmtime/src/compiler.rs @@ -24,7 +24,7 @@ use crate::Engine; use anyhow::Result; -use std::collections::{btree_map, BTreeMap, BTreeSet, HashSet}; +use std::collections::{btree_map, BTreeMap, BTreeSet}; use std::{any::Any, collections::HashMap}; use wasmtime_environ::{ Compiler, DefinedFuncIndex, FuncIndex, FunctionBodyData, ModuleTranslation, ModuleType, @@ -33,7 +33,7 @@ use wasmtime_environ::{ use wasmtime_jit::{CompiledFunctionInfo, CompiledModuleInfo}; type CompileInput<'a> = - Box Result + Send + 'a>; + Box Result + Send + 'a>; /// A sortable, comparable key for a compilation output. /// @@ -204,18 +204,15 @@ struct CompileOutput { /// The collection of things we need to compile for a Wasm module or component. #[derive(Default)] pub struct CompileInputs<'a> { - inputs: Vec<(CompileKey, CompileInput<'a>)>, - input_keys: HashSet, + inputs: Vec>, } impl<'a> CompileInputs<'a> { fn push_input( &mut self, - key: CompileKey, - f: impl FnOnce(CompileKey, &Tunables, &dyn Compiler) -> Result + Send + 'a, + f: impl FnOnce(&Tunables, &dyn Compiler) -> Result + Send + 'a, ) { - assert!(self.input_keys.insert(key)); - self.inputs.push((key, Box::new(f))); + self.inputs.push(Box::new(f)); } /// Create the `CompileInputs` for a core Wasm module. @@ -252,10 +249,9 @@ impl<'a> CompileInputs<'a> { for init in &component.initializers { match init { wasmtime_environ::component::GlobalInitializer::AlwaysTrap(always_trap) => { - let key = CompileKey::always_trap(always_trap.index); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { - key, + key: CompileKey::always_trap(always_trap.index), symbol: always_trap.symbol_name(), function: compiler .component_compiler() @@ -266,10 +262,9 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::Transcoder(transcoder) => { - let key = CompileKey::transcoder(transcoder.index); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { - key, + key: CompileKey::transcoder(transcoder.index), symbol: transcoder.symbol_name(), function: compiler .component_compiler() @@ -280,10 +275,9 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::LowerImport(lower_import) => { - let key = CompileKey::lowering(lower_import.index); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { - key, + key: CompileKey::lowering(lower_import.index), symbol: lower_import.symbol_name(), function: compiler .component_compiler() @@ -295,10 +289,9 @@ impl<'a> CompileInputs<'a> { } wasmtime_environ::component::GlobalInitializer::ResourceNew(r) => { - let key = CompileKey::resource_new(r.index); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { - key, + key: CompileKey::resource_new(r.index), symbol: r.symbol_name(), function: compiler .component_compiler() @@ -309,10 +302,9 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::ResourceRep(r) => { - let key = CompileKey::resource_rep(r.index); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { - key, + key: CompileKey::resource_rep(r.index), symbol: r.symbol_name(), function: compiler .component_compiler() @@ -323,10 +315,9 @@ impl<'a> CompileInputs<'a> { }); } wasmtime_environ::component::GlobalInitializer::ResourceDrop(r) => { - let key = CompileKey::resource_drop(r.index); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { Ok(CompileOutput { - key, + key: CompileKey::resource_drop(r.index), symbol: r.symbol_name(), function: compiler .component_compiler() @@ -361,11 +352,10 @@ impl<'a> CompileInputs<'a> { // so it's an accepted overhead for now. if component.num_resources > 0 { if let Some(sig) = types.find_resource_drop_signature() { - let key = CompileKey::resource_drop_wasm_to_native_trampoline(); - ret.push_input(key, move |key, _tunables, compiler| { + ret.push_input(move |_tunables, compiler| { let trampoline = compiler.compile_wasm_to_native_trampoline(&types[sig])?; Ok(CompileOutput { - key, + key: CompileKey::resource_drop_wasm_to_native_trampoline(), symbol: "resource_drop_trampoline".to_string(), function: CompiledFunction::Function(trampoline), info: None, @@ -392,8 +382,7 @@ impl<'a> CompileInputs<'a> { for (module, translation, functions) in translations { for (def_func_index, func_body) in functions { - let key = CompileKey::wasm_function(module, def_func_index); - self.push_input(key, move |key, 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, @@ -403,7 +392,7 @@ impl<'a> CompileInputs<'a> { types, )?; Ok(CompileOutput { - key, + key: CompileKey::wasm_function(module, def_func_index), symbol: format!( "wasm[{}]::function[{}]", module.as_u32(), @@ -416,8 +405,7 @@ impl<'a> CompileInputs<'a> { let func_index = translation.module.func_index(def_func_index); if translation.module.functions[func_index].is_escaping() { - let key = CompileKey::array_to_wasm_trampoline(module, def_func_index); - self.push_input(key, move |key, _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, @@ -425,7 +413,7 @@ impl<'a> CompileInputs<'a> { def_func_index, )?; Ok(CompileOutput { - key, + key: CompileKey::array_to_wasm_trampoline(module, def_func_index), symbol: format!( "wasm[{}]::array_to_wasm_trampoline[{}]", module.as_u32(), @@ -436,8 +424,7 @@ impl<'a> CompileInputs<'a> { }) }); - let key = CompileKey::native_to_wasm_trampoline(module, def_func_index); - self.push_input(key, move |key, _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, @@ -445,7 +432,7 @@ impl<'a> CompileInputs<'a> { def_func_index, )?; Ok(CompileOutput { - key, + key: CompileKey::native_to_wasm_trampoline(module, def_func_index), symbol: format!( "wasm[{}]::native_to_wasm_trampoline[{}]", module.as_u32(), @@ -464,12 +451,11 @@ impl<'a> CompileInputs<'a> { } for signature in sigs { - let key = CompileKey::wasm_to_native_trampoline(signature); - self.push_input(key, move |key, _tunables, compiler| { + self.push_input(move |_tunables, compiler| { let wasm_func_ty = &types[signature]; let trampoline = compiler.compile_wasm_to_native_trampoline(wasm_func_ty)?; Ok(CompileOutput { - key, + key: CompileKey::wasm_to_native_trampoline(signature), symbol: format!( "signatures[{}]::wasm_to_native_trampoline", signature.as_u32() @@ -488,8 +474,7 @@ impl<'a> CompileInputs<'a> { let compiler = engine.compiler(); // Compile each individual input in parallel. - let raw_outputs = - engine.run_maybe_parallel(self.inputs, |(key, f)| f(key, tunables, compiler))?; + let raw_outputs = engine.run_maybe_parallel(self.inputs, |f| f(tunables, compiler))?; // Bucket the outputs by kind. let mut outputs: BTreeMap> = BTreeMap::new(); From 9492a87de9ef5471200a62b9a2167bcda182b3da Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 13:27:25 -0700 Subject: [PATCH 45/47] Gate an async function on the async feature --- crates/wasmtime/src/component/resources.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index ea41df21cd13..ded0665a66a1 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -528,6 +528,8 @@ impl ResourceAny { /// 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, From 96f57f13179494faa997804e14f13ad2976dfae8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 13:58:25 -0700 Subject: [PATCH 46/47] Fix doc links --- crates/wasmtime/src/component/linker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs index 109efaa6a943..15469923fe8a 100644 --- a/crates/wasmtime/src/component/linker.rs +++ b/crates/wasmtime/src/component/linker.rs @@ -395,7 +395,7 @@ impl LinkerInstance<'_, T> { /// 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 + /// [`Resource`]: crate::component::Resource pub fn resource( &mut self, name: &str, From 5b4cff6ffd32d0ac6e2fc6c95c51ee4e7a99dc59 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 21 Jul 2023 14:58:43 -0700 Subject: [PATCH 47/47] Skip resources tests in miri They all involve compilation which takes too long and doesn't currently work --- tests/all/component_model/resources.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 5b3a0a5110ac..e8dd9de4dd4b 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -1,3 +1,5 @@ +#![cfg(not(miri))] + use anyhow::Result; use wasmtime::component::*; use wasmtime::{Store, Trap};