From 015b27149430289329381e4ce665ee7abf9b2743 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 26 Mar 2024 15:20:58 -0600 Subject: [PATCH 1/5] Add support for async ABI, futures, streams, and errors This adds support for encoding and parsing components which use the [Async ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) and associated canonical options and functions, along with the [`stream`, `future`, and `error`](https://github.com/WebAssembly/component-model/pull/405) types. Note that the `error` type was recently (about 30 minutes ago) renamed to `error-context` in Luke's spec PR. I haven't updated this implementation to reflect that yet, but will do so in a follow-up commit. That should allow us to avoid conflicts with existing WIT files that use `error` as a type and/or interface name. This does not include any new tests; I'll also add those in a follow-up commit. See https://github.com/bytecodealliance/rfcs/pull/38 for more context. Signed-off-by: Joel Dice --- crates/wasm-compose/src/composer.rs | 2 - crates/wasm-compose/src/encoding.rs | 69 +- crates/wasm-compose/src/graph.rs | 54 +- crates/wasm-encoder/src/component/builder.rs | 164 +++- .../wasm-encoder/src/component/canonicals.rs | 280 ++++++ crates/wasm-encoder/src/component/types.rs | 17 + crates/wasm-encoder/src/reencode/component.rs | 92 ++ crates/wasmparser/src/features.rs | 2 + crates/wasmparser/src/readers.rs | 13 + .../src/readers/component/canonicals.rs | 235 +++++ .../wasmparser/src/readers/component/types.rs | 9 + crates/wasmparser/src/validator.rs | 78 +- crates/wasmparser/src/validator/component.rs | 867 +++++++++++++++++- .../src/validator/component_types.rs | 138 ++- crates/wasmprinter/src/component.rs | 286 ++++++ crates/wit-component/src/encoding.rs | 599 +++++++++++- crates/wit-component/src/encoding/types.rs | 29 +- crates/wit-component/src/encoding/world.rs | 25 +- crates/wit-component/src/metadata.rs | 18 + crates/wit-component/src/printing.rs | 7 +- crates/wit-component/src/validation.rs | 597 +++++++++++- .../wasi-http/deps/filesystem/types.wit | 4 +- .../interfaces/wasi-http/deps/io/error.wit | 4 +- .../interfaces/wasi-http/deps/io/streams.wit | 4 +- .../tests/interfaces/wasi-http/http.wit.print | 4 +- .../tests/interfaces/wasi-http/types.wit | 2 +- crates/wit-encoder/src/from_parser.rs | 22 +- crates/wit-encoder/src/ty.rs | 19 + crates/wit-parser/src/abi.rs | 32 +- crates/wit-parser/src/ast.rs | 35 +- crates/wit-parser/src/ast/lex.rs | 3 + crates/wit-parser/src/ast/resolve.rs | 28 +- crates/wit-parser/src/decoding.rs | 42 +- crates/wit-parser/src/lib.rs | 87 +- crates/wit-parser/src/live.rs | 16 +- crates/wit-parser/src/resolve.rs | 28 +- crates/wit-parser/src/resolve/clone.rs | 6 +- crates/wit-parser/src/sizealign.rs | 8 +- crates/wit-parser/tests/ui/comments.wit.json | 5 +- crates/wit-parser/tests/ui/types.wit | 9 +- crates/wit-parser/tests/ui/types.wit.json | 52 +- src/bin/wasm-tools/dump.rs | 23 + .../cli/validate-unknown-features.wat.stderr | 2 +- 43 files changed, 3718 insertions(+), 298 deletions(-) diff --git a/crates/wasm-compose/src/composer.rs b/crates/wasm-compose/src/composer.rs index 32fc801b27..84ce8339df 100644 --- a/crates/wasm-compose/src/composer.rs +++ b/crates/wasm-compose/src/composer.rs @@ -497,8 +497,6 @@ impl<'a> CompositionGraphBuilder<'a> { } } - self.graph.unify_imported_resources(); - Ok((self.instances[root_instance], self.graph)) } } diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 93dc5c6ab0..53d30a34b4 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -596,6 +596,17 @@ impl<'a> TypeEncoder<'a> { return ret; } + if let Some((instance, name)) = state.cur.instance_exports.get(&key) { + let ret = state.cur.encodable.type_count(); + state.cur.encodable.alias(Alias::InstanceExport { + instance: *instance, + name, + kind: ComponentExportKind::Type, + }); + log::trace!("id defined in current instance"); + return ret; + } + match id.peel_alias(&self.0.types) { Some(next) => id = next, // If there's no more aliases then fall through to the @@ -608,15 +619,17 @@ impl<'a> TypeEncoder<'a> { return match id { AnyTypeId::Core(ComponentCoreTypeId::Sub(_)) => unreachable!(), AnyTypeId::Core(ComponentCoreTypeId::Module(id)) => self.module_type(state, id), - AnyTypeId::Component(id) => match id { - ComponentAnyTypeId::Resource(_) => { - unreachable!("should have been handled in `TypeEncoder::component_entity_type`") + AnyTypeId::Component(id) => { + match id { + ComponentAnyTypeId::Resource(r) => { + unreachable!("should have been handled in `TypeEncoder::component_entity_type`: {r:?}") + } + ComponentAnyTypeId::Defined(id) => self.defined_type(state, id), + ComponentAnyTypeId::Func(id) => self.component_func_type(state, id), + ComponentAnyTypeId::Instance(id) => self.component_instance_type(state, id), + ComponentAnyTypeId::Component(id) => self.component_type(state, id), } - ComponentAnyTypeId::Defined(id) => self.defined_type(state, id), - ComponentAnyTypeId::Func(id) => self.component_func_type(state, id), - ComponentAnyTypeId::Instance(id) => self.component_instance_type(state, id), - ComponentAnyTypeId::Component(id) => self.component_type(state, id), - }, + } }; } @@ -667,6 +680,9 @@ impl<'a> TypeEncoder<'a> { state.cur.encodable.ty().defined_type().borrow(ty); index } + ComponentDefinedType::Future(ty) => self.future(state, *ty), + ComponentDefinedType::Stream(ty) => self.stream(state, *ty), + ComponentDefinedType::Error => self.error(state), } } @@ -788,6 +804,28 @@ impl<'a> TypeEncoder<'a> { } export } + + fn future(&self, state: &mut TypeState<'a>, ty: Option) -> u32 { + let ty = ty.map(|ty| self.component_val_type(state, ty)); + + let index = state.cur.encodable.type_count(); + state.cur.encodable.ty().defined_type().future(ty); + index + } + + fn stream(&self, state: &mut TypeState<'a>, ty: ct::ComponentValType) -> u32 { + let ty = self.component_val_type(state, ty); + + let index = state.cur.encodable.type_count(); + state.cur.encodable.ty().defined_type().stream(ty); + index + } + + fn error(&self, state: &mut TypeState<'a>) -> u32 { + let index = state.cur.encodable.type_count(); + state.cur.encodable.ty().defined_type().error(); + index + } } /// Represents an instance index in a composition graph. @@ -1215,8 +1253,11 @@ impl DependencyRegistrar<'_, '_> { match &self.types[ty] { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Enum(_) - | ComponentDefinedType::Flags(_) => {} - ComponentDefinedType::List(t) | ComponentDefinedType::Option(t) => self.val_type(*t), + | ComponentDefinedType::Flags(_) + | ComponentDefinedType::Error => {} + ComponentDefinedType::List(t) + | ComponentDefinedType::Option(t) + | ComponentDefinedType::Stream(t) => self.val_type(*t), ComponentDefinedType::Own(r) | ComponentDefinedType::Borrow(r) => { self.ty(ComponentAnyTypeId::Resource(*r)) } @@ -1245,6 +1286,11 @@ impl DependencyRegistrar<'_, '_> { self.val_type(*err); } } + ComponentDefinedType::Future(ty) => { + if let Some(ty) = ty { + self.val_type(*ty); + } + } } } } @@ -1402,7 +1448,7 @@ impl<'a> CompositionGraphEncoder<'a> { state.push(Encodable::Instance(InstanceType::new())); for (name, types) in exports { let (component, ty) = types[0]; - log::trace!("export {name}"); + log::trace!("export {name}: {ty:?}"); let export = TypeEncoder::new(component).export(name, ty, state); let t = match &mut state.cur.encodable { Encodable::Instance(c) => c, @@ -1418,6 +1464,7 @@ impl<'a> CompositionGraphEncoder<'a> { } } } + let instance_type = match state.pop() { Encodable::Instance(c) => c, _ => unreachable!(), diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs index c346a623a1..8bb1dcdbb8 100644 --- a/crates/wasm-compose/src/graph.rs +++ b/crates/wasm-compose/src/graph.rs @@ -18,7 +18,7 @@ use wasmparser::{ names::ComponentName, types::{Types, TypesRef}, Chunk, ComponentExternalKind, ComponentTypeRef, Encoding, Parser, Payload, ValidPayload, - Validator, + Validator, WasmFeatures, }; pub(crate) fn type_desc(item: ComponentEntityType) -> &'static str { @@ -99,7 +99,11 @@ impl<'a> Component<'a> { fn parse(name: String, path: Option, bytes: Cow<'a, [u8]>) -> Result { let mut parser = Parser::new(0); let mut parsers = Vec::new(); - let mut validator = Validator::new(); + let mut validator = Validator::new_with_features( + WasmFeatures::WASM2 + | WasmFeatures::COMPONENT_MODEL + | WasmFeatures::COMPONENT_MODEL_ASYNC, + ); let mut imports = IndexMap::new(); let mut exports = IndexMap::new(); @@ -439,7 +443,7 @@ pub(crate) struct Instance { } /// The options for encoding a composition graph. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub struct EncodeOptions { /// Whether or not to define instantiated components. /// @@ -448,7 +452,7 @@ pub struct EncodeOptions { /// The instance in the graph to export. /// - /// If `Some`, the instance's exports will be aliased and + /// If non-empty, the instance's exports will be aliased and /// exported from the resulting component. pub export: Option, @@ -508,9 +512,6 @@ impl ResourceMapping { if value.1 == export_resource { self.map.insert(export_resource, value); self.map.insert(import_resource, value); - } else { - // Can't set two different exports equal to each other -- give up. - return None; } } else { // Couldn't find an export with a name that matches this @@ -559,14 +560,19 @@ impl<'a> CompositionGraph<'a> { /// connected to exports, group them by name, and update the resource /// mapping to make all resources within each group equivalent. /// - /// This should be the last step prior to encoding, after all - /// inter-component connections have been made. It ensures that each set of - /// identical imports composed component can be merged into a single import - /// in the output component. + /// This ensures that each set of identical imports in the composed + /// components can be merged into a single import in the output component. + // + // TODO: How do we balance the need to call this early (so we can match up + // imports with exports which mutually import the same resources) with the + // need to delay decisions about where resources are coming from (so that we + // can match up imported resources with exported resources)? Right now I + // think we're erring on the side if the former at the expense of the + // latter. pub(crate) fn unify_imported_resources(&self) { let mut resource_mapping = self.resource_mapping.borrow_mut(); - let mut resource_imports = HashMap::<_, Vec<_>>::new(); + let mut resource_imports = IndexMap::<_, IndexSet<_>>::new(); for (component_id, component) in &self.components { let component = &component.component; for import_name in component.imports.keys() { @@ -584,12 +590,14 @@ impl<'a> CompositionGraph<'a> { .. } = ty { - if !resource_mapping.map.contains_key(&resource_id.resource()) { - resource_imports - .entry(vec![import_name.to_string(), export_name.to_string()]) - .or_default() - .push((*component_id, resource_id.resource())) + let set = resource_imports + .entry(vec![import_name.to_string(), export_name.to_string()]) + .or_default(); + + if let Some(pair) = resource_mapping.map.get(&resource_id.resource()) { + set.insert(*pair); } + set.insert((*component_id, resource_id.resource())); } } } @@ -597,7 +605,7 @@ impl<'a> CompositionGraph<'a> { } for resources in resource_imports.values() { - match &resources[..] { + match &resources.iter().copied().collect::>()[..] { [] => unreachable!(), [_] => {} [first, rest @ ..] => { @@ -653,10 +661,8 @@ impl<'a> CompositionGraph<'a> { .remap_component_entity(&mut import_type, remapping); remapping.reset_type_cache(); - if context - .component_entity_type(&export_type, &import_type, 0) - .is_ok() - { + let v = context.component_entity_type(&export_type, &import_type, 0); + if v.is_ok() { *self.resource_mapping.borrow_mut() = resource_mapping; true } else { @@ -706,6 +712,10 @@ impl<'a> CompositionGraph<'a> { assert!(self.components.insert(id, entry).is_none()); + if self.components.len() > 1 { + self.unify_imported_resources(); + } + Ok(id) } diff --git a/crates/wasm-encoder/src/component/builder.rs b/crates/wasm-encoder/src/component/builder.rs index d587ae99f5..b0fab33083 100644 --- a/crates/wasm-encoder/src/component/builder.rs +++ b/crates/wasm-encoder/src/component/builder.rs @@ -316,7 +316,7 @@ impl ComponentBuilder { (inc(&mut self.types), self.types().function()) } - /// Declares a + /// Declares a new resource type within this component. pub fn type_resource(&mut self, rep: ValType, dtor: Option) -> u32 { self.types().resource(rep, dtor); inc(&mut self.types) @@ -384,6 +384,168 @@ impl ComponentBuilder { inc(&mut self.core_funcs) } + /// Declares a new `task.backpressure` intrinsic. + pub fn task_backpressure(&mut self) -> u32 { + self.canonical_functions().task_backpressure(); + inc(&mut self.core_funcs) + } + + /// Declares a new `task.return` intrinsic. + pub fn task_return(&mut self, ty: u32) -> u32 { + self.canonical_functions().task_return(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `task.wait` intrinsic. + pub fn task_wait(&mut self, async_: bool, memory: u32) -> u32 { + self.canonical_functions().task_wait(async_, memory); + inc(&mut self.core_funcs) + } + + /// Declares a new `task.poll` intrinsic. + pub fn task_poll(&mut self, async_: bool, memory: u32) -> u32 { + self.canonical_functions().task_poll(async_, memory); + inc(&mut self.core_funcs) + } + + /// Declares a new `task.yield` intrinsic. + pub fn task_yield(&mut self, async_: bool) -> u32 { + self.canonical_functions().task_yield(async_); + inc(&mut self.core_funcs) + } + + /// Declares a new `subtask.drop` intrinsic. + pub fn subtask_drop(&mut self) -> u32 { + self.canonical_functions().subtask_drop(); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.new` intrinsic. + pub fn stream_new(&mut self, ty: u32) -> u32 { + self.canonical_functions().stream_new(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.read` intrinsic. + pub fn stream_read(&mut self, ty: u32, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().stream_read(ty, options); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.write` intrinsic. + pub fn stream_write(&mut self, ty: u32, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().stream_write(ty, options); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.cancel-read` intrinsic. + pub fn stream_cancel_read(&mut self, ty: u32, async_: bool) -> u32 { + self.canonical_functions().stream_cancel_read(ty, async_); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.cancel-write` intrinsic. + pub fn stream_cancel_write(&mut self, ty: u32, async_: bool) -> u32 { + self.canonical_functions().stream_cancel_read(ty, async_); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.close-readable` intrinsic. + pub fn stream_close_readable(&mut self, ty: u32) -> u32 { + self.canonical_functions().stream_close_readable(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `stream.close-writable` intrinsic. + pub fn stream_close_writable(&mut self, ty: u32) -> u32 { + self.canonical_functions().stream_close_writable(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.new` intrinsic. + pub fn future_new(&mut self, ty: u32) -> u32 { + self.canonical_functions().future_new(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.read` intrinsic. + pub fn future_read(&mut self, ty: u32, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().future_read(ty, options); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.write` intrinsic. + pub fn future_write(&mut self, ty: u32, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().future_write(ty, options); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.cancel-read` intrinsic. + pub fn future_cancel_read(&mut self, ty: u32, async_: bool) -> u32 { + self.canonical_functions().future_cancel_read(ty, async_); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.cancel-write` intrinsic. + pub fn future_cancel_write(&mut self, ty: u32, async_: bool) -> u32 { + self.canonical_functions().future_cancel_read(ty, async_); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.close-readable` intrinsic. + pub fn future_close_readable(&mut self, ty: u32) -> u32 { + self.canonical_functions().future_close_readable(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `future.close-writable` intrinsic. + pub fn future_close_writable(&mut self, ty: u32) -> u32 { + self.canonical_functions().future_close_writable(ty); + inc(&mut self.core_funcs) + } + + /// Declares a new `error.new` intrinsic. + pub fn error_new(&mut self, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().error_new(options); + inc(&mut self.core_funcs) + } + + /// Declares a new `error.debug-message` intrinsic. + pub fn error_debug_message(&mut self, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().error_debug_message(options); + inc(&mut self.core_funcs) + } + + /// Declares a new `error.drop` intrinsic. + pub fn error_drop(&mut self) -> u32 { + self.canonical_functions().error_drop(); + inc(&mut self.core_funcs) + } + /// Adds a new custom section to this component. pub fn custom_section(&mut self, section: &CustomSection<'_>) { self.flush(); diff --git a/crates/wasm-encoder/src/component/canonicals.rs b/crates/wasm-encoder/src/component/canonicals.rs index 8bb4ad2c89..748c672898 100644 --- a/crates/wasm-encoder/src/component/canonicals.rs +++ b/crates/wasm-encoder/src/component/canonicals.rs @@ -21,6 +21,11 @@ pub enum CanonicalOption { /// The post-return function to use if the lifting of a function requires /// cleanup after the function returns. PostReturn(u32), + /// Indicates that specified function should be lifted or lowered using the `async` ABI. + Async, + /// The function to use if the async lifting of a function should receive task/stream/future progress events + /// using a callback. + Callback(u32), } impl Encode for CanonicalOption { @@ -41,6 +46,13 @@ impl Encode for CanonicalOption { sink.push(0x05); idx.encode(sink); } + Self::Async => { + sink.push(0x06); + } + Self::Callback(idx) => { + sink.push(0x07); + idx.encode(sink); + } } } } @@ -161,6 +173,274 @@ impl CanonicalFunctionSection { self.num_added += 1; self } + + /// Defines a function which tells the host to enable or disable + /// backpressure for the caller's instance. When backpressure is enabled, + /// the host must not start any new calls to that instance until + /// backpressure is disabled. + pub fn task_backpressure(&mut self) -> &mut Self { + self.bytes.push(0x08); + self.num_added += 1; + self + } + + /// Defines a function which returns a result to the caller of a lifted + /// export function. This allows the callee to continue executing after + /// returning a result. + pub fn task_return(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x09); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function which waits for at least one outstanding async + /// task/stream/future to make progress, returning the first such event. + /// + /// If `async_` is true, the caller instance may be reentered. + pub fn task_wait(&mut self, async_: bool, memory: u32) -> &mut Self { + self.bytes.push(0x0a); + self.bytes.push(if async_ { 1 } else { 0 }); + memory.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function which checks whether any outstanding async + /// task/stream/future has made progress. Unlike `task.wait`, this does not + /// block and may return nothing if no such event has occurred. + /// + /// If `async_` is true, the caller instance may be reentered. + pub fn task_poll(&mut self, async_: bool, memory: u32) -> &mut Self { + self.bytes.push(0x0b); + self.bytes.push(if async_ { 1 } else { 0 }); + memory.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function which yields control to the host so that other tasks + /// are able to make progress, if any. + /// + /// If `async_` is true, the caller instance may be reentered. + pub fn task_yield(&mut self, async_: bool) -> &mut Self { + self.bytes.push(0x0c); + self.bytes.push(if async_ { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Defines a function to drop a specified task which has completed. + pub fn subtask_drop(&mut self) -> &mut Self { + self.bytes.push(0x0d); + self.num_added += 1; + self + } + + /// Defines a function to create a new `stream` handle of the specified + /// type. + pub fn stream_new(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x0e); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function to read from a `stream` of the specified type. + pub fn stream_read(&mut self, ty: u32, options: O) -> &mut Self + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.bytes.push(0x0f); + ty.encode(&mut self.bytes); + let options = options.into_iter(); + options.len().encode(&mut self.bytes); + for option in options { + option.encode(&mut self.bytes); + } + self.num_added += 1; + self + } + + /// Defines a function to write to a `stream` of the specified type. + pub fn stream_write(&mut self, ty: u32, options: O) -> &mut Self + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.bytes.push(0x10); + ty.encode(&mut self.bytes); + let options = options.into_iter(); + options.len().encode(&mut self.bytes); + for option in options { + option.encode(&mut self.bytes); + } + self.num_added += 1; + self + } + + /// Defines a function to cancel an in-progress read from a `stream` of the + /// specified type. + pub fn stream_cancel_read(&mut self, ty: u32, async_: bool) -> &mut Self { + self.bytes.push(0x11); + ty.encode(&mut self.bytes); + self.bytes.push(if async_ { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Defines a function to cancel an in-progress write to a `stream` of the + /// specified type. + pub fn stream_cancel_write(&mut self, ty: u32, async_: bool) -> &mut Self { + self.bytes.push(0x12); + ty.encode(&mut self.bytes); + self.bytes.push(if async_ { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Defines a function to close the readable end of a `stream` of the + /// specified type. + pub fn stream_close_readable(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x13); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function to close the writable end of a `stream` of the + /// specified type. + pub fn stream_close_writable(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x14); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function to create a new `future` handle of the specified + /// type. + pub fn future_new(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x15); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function to read from a `future` of the specified type. + pub fn future_read(&mut self, ty: u32, options: O) -> &mut Self + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.bytes.push(0x16); + ty.encode(&mut self.bytes); + let options = options.into_iter(); + options.len().encode(&mut self.bytes); + for option in options { + option.encode(&mut self.bytes); + } + self.num_added += 1; + self + } + + /// Defines a function to write to a `future` of the specified type. + pub fn future_write(&mut self, ty: u32, options: O) -> &mut Self + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.bytes.push(0x17); + ty.encode(&mut self.bytes); + let options = options.into_iter(); + options.len().encode(&mut self.bytes); + for option in options { + option.encode(&mut self.bytes); + } + self.num_added += 1; + self + } + + /// Defines a function to cancel an in-progress read from a `future` of the + /// specified type. + pub fn future_cancel_read(&mut self, ty: u32, async_: bool) -> &mut Self { + self.bytes.push(0x18); + ty.encode(&mut self.bytes); + self.bytes.push(if async_ { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Defines a function to cancel an in-progress write to a `future` of the + /// specified type. + pub fn future_cancel_write(&mut self, ty: u32, async_: bool) -> &mut Self { + self.bytes.push(0x19); + ty.encode(&mut self.bytes); + self.bytes.push(if async_ { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Defines a function to close the readable end of a `future` of the + /// specified type. + pub fn future_close_readable(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x1a); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function to close the writable end of a `future` of the + /// specified type. + pub fn future_close_writable(&mut self, ty: u32) -> &mut Self { + self.bytes.push(0x1b); + ty.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Defines a function to create a new `error` with a specified debug + /// message. + pub fn error_new(&mut self, options: O) -> &mut Self + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.bytes.push(0x1c); + let options = options.into_iter(); + options.len().encode(&mut self.bytes); + for option in options { + option.encode(&mut self.bytes); + } + self.num_added += 1; + self + } + + /// Defines a function to get the debug message for a specified `error`. + /// + /// Note that the debug message might not necessarily match what was passed + /// to `error.new`. + pub fn error_debug_message(&mut self, options: O) -> &mut Self + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.bytes.push(0x1d); + let options = options.into_iter(); + options.len().encode(&mut self.bytes); + for option in options { + option.encode(&mut self.bytes); + } + self.num_added += 1; + self + } + + /// Defines a function to drop a specified `error`. + pub fn error_drop(&mut self) -> &mut Self { + self.bytes.push(0x1e); + self.num_added += 1; + self + } } impl Encode for CanonicalFunctionSection { diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index 39853d5f7b..3df657c8a1 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -671,6 +671,23 @@ impl ComponentDefinedTypeEncoder<'_> { self.0.push(0x68); idx.encode(self.0); } + + /// Define a `future` type with the specified payload. + pub fn future(self, payload: Option) { + self.0.push(0x67); + payload.encode(self.0); + } + + /// Define a `stream` type with the specified payload. + pub fn stream(self, payload: ComponentValType) { + self.0.push(0x66); + payload.encode(self.0); + } + + /// Define the `error` type. + pub fn error(self) { + self.0.push(0x65); + } } /// An encoder for the type section of WebAssembly components. diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index d1c4d369e9..277db62fcb 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -797,6 +797,13 @@ pub mod component_utils { wasmparser::ComponentDefinedType::Borrow(i) => { defined.borrow(reencoder.component_type_index(i)); } + wasmparser::ComponentDefinedType::Future(t) => { + defined.future(t.map(|t| reencoder.component_val_type(t))); + } + wasmparser::ComponentDefinedType::Stream(t) => { + defined.stream(reencoder.component_val_type(t)); + } + wasmparser::ComponentDefinedType::Error => defined.error(), } Ok(()) } @@ -957,6 +964,87 @@ pub mod component_utils { wasmparser::CanonicalFunction::ThreadHwConcurrency => { section.thread_hw_concurrency(); } + wasmparser::CanonicalFunction::TaskBackpressure => { + section.task_backpressure(); + } + wasmparser::CanonicalFunction::TaskReturn { type_index } => { + section.task_return(reencoder.type_index(type_index)); + } + wasmparser::CanonicalFunction::TaskWait { async_, memory } => { + section.task_wait(async_, reencoder.memory_index(memory)); + } + wasmparser::CanonicalFunction::TaskPoll { async_, memory } => { + section.task_poll(async_, reencoder.memory_index(memory)); + } + wasmparser::CanonicalFunction::TaskYield { async_ } => { + section.task_yield(async_); + } + wasmparser::CanonicalFunction::SubtaskDrop => { + section.subtask_drop(); + } + wasmparser::CanonicalFunction::StreamNew { ty } => { + section.stream_new(reencoder.component_type_index(ty)); + } + wasmparser::CanonicalFunction::StreamRead { ty, options } => { + section.stream_read( + reencoder.component_type_index(ty), + options.iter().map(|o| reencoder.canonical_option(*o)), + ); + } + wasmparser::CanonicalFunction::StreamWrite { ty, options } => { + section.stream_write( + reencoder.component_type_index(ty), + options.iter().map(|o| reencoder.canonical_option(*o)), + ); + } + wasmparser::CanonicalFunction::StreamCancelRead { ty, async_ } => { + section.stream_cancel_read(ty, async_); + } + wasmparser::CanonicalFunction::StreamCancelWrite { ty, async_ } => { + section.stream_cancel_write(ty, async_); + } + wasmparser::CanonicalFunction::StreamCloseReadable { ty } => { + section.stream_close_readable(reencoder.component_type_index(ty)); + } + wasmparser::CanonicalFunction::StreamCloseWritable { ty } => { + section.stream_close_writable(reencoder.component_type_index(ty)); + } + wasmparser::CanonicalFunction::FutureNew { ty } => { + section.future_new(reencoder.component_type_index(ty)); + } + wasmparser::CanonicalFunction::FutureRead { ty, options } => { + section.future_read( + reencoder.component_type_index(ty), + options.iter().map(|o| reencoder.canonical_option(*o)), + ); + } + wasmparser::CanonicalFunction::FutureWrite { ty, options } => { + section.future_write( + reencoder.component_type_index(ty), + options.iter().map(|o| reencoder.canonical_option(*o)), + ); + } + wasmparser::CanonicalFunction::FutureCancelRead { ty, async_ } => { + section.future_cancel_read(ty, async_); + } + wasmparser::CanonicalFunction::FutureCancelWrite { ty, async_ } => { + section.future_cancel_write(ty, async_); + } + wasmparser::CanonicalFunction::FutureCloseReadable { ty } => { + section.future_close_readable(reencoder.component_type_index(ty)); + } + wasmparser::CanonicalFunction::FutureCloseWritable { ty } => { + section.future_close_writable(reencoder.component_type_index(ty)); + } + wasmparser::CanonicalFunction::ErrorNew { options } => { + section.error_new(options.iter().map(|o| reencoder.canonical_option(*o))); + } + wasmparser::CanonicalFunction::ErrorDebugMessage { options } => { + section.error_debug_message(options.iter().map(|o| reencoder.canonical_option(*o))); + } + wasmparser::CanonicalFunction::ErrorDrop => { + section.error_drop(); + } } Ok(()) } @@ -1239,6 +1327,10 @@ pub mod component_utils { wasmparser::CanonicalOption::PostReturn(u) => { crate::component::CanonicalOption::PostReturn(reencoder.function_index(u)) } + wasmparser::CanonicalOption::Async => crate::component::CanonicalOption::Async, + wasmparser::CanonicalOption::Callback(u) => { + crate::component::CanonicalOption::Callback(reencoder.function_index(u)) + } } } diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index 5de7237b86..89fffb7c24 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -229,6 +229,8 @@ define_wasm_features! { pub stack_switching: STACK_SWITCHING(1 << 27) = false; /// The WebAssembly [wide-arithmetic proposal](https://github.com/WebAssembly/wide-arithmetic). pub wide_arithmetic: WIDE_ARITHMETIC(1 << 28) = false; + /// Support for component model async lift/lower ABI, as well as streams, futures, and errors. + pub component_model_async: COMPONENT_MODEL_ASYNC(1 << 29) = false; } } diff --git a/crates/wasmparser/src/readers.rs b/crates/wasmparser/src/readers.rs index b4a1ab7ba4..4213b33d74 100644 --- a/crates/wasmparser/src/readers.rs +++ b/crates/wasmparser/src/readers.rs @@ -37,6 +37,19 @@ pub trait FromReader<'a>: Sized { fn from_reader(reader: &mut BinaryReader<'a>) -> Result; } +impl<'a> FromReader<'a> for bool { + fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + match reader.read_u8()? { + 0 => Ok(false), + 1 => Ok(true), + v => Err(BinaryReaderError::new( + format!("invalid boolean value: {v}"), + reader.original_position() - 1, + )), + } + } +} + impl<'a> FromReader<'a> for u32 { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { reader.read_var_u32() diff --git a/crates/wasmparser/src/readers/component/canonicals.rs b/crates/wasmparser/src/readers/component/canonicals.rs index a019abbaac..d14a447ad0 100644 --- a/crates/wasmparser/src/readers/component/canonicals.rs +++ b/crates/wasmparser/src/readers/component/canonicals.rs @@ -23,6 +23,11 @@ pub enum CanonicalOption { /// The post-return function to use if the lifting of a function requires /// cleanup after the function returns. PostReturn(u32), + /// Indicates that specified function should be lifted or lowered using the `async` ABI. + Async, + /// The function to use if the async lifting of a function should receive task/stream/future progress events + /// using a callback. + Callback(u32), } /// Represents a canonical function in a WebAssembly component. @@ -68,6 +73,161 @@ pub enum CanonicalFunction { /// A function which returns the number of threads that can be expected to /// execute concurrently ThreadHwConcurrency, + /// A function which tells the host to enable or disable backpressure for + /// the caller's instance. + TaskBackpressure, + /// A function which returns a result to the caller of a lifted export + /// function. This allows the callee to continue executing after returning + /// a result. + TaskReturn { + /// Core function type whose parameters represent the flattened + /// representation of the component-level results to be returned by the + /// currently executing task. + type_index: u32, + }, + /// A function which waits for at least one outstanding async + /// task/stream/future to make progress, returning the first such event. + TaskWait { + /// If `true`, indicates the caller instance maybe reentered. + async_: bool, + /// Memory to use when storing the event. + memory: u32, + }, + /// A function which checks whether any outstanding async task/stream/future + /// has made progress. Unlike `task.wait`, this does not block and may + /// return nothing if no such event has occurred. + TaskPoll { + /// If `true`, indicates the caller instance maybe reentered. + async_: bool, + /// Memory to use when storing the event, if any. + memory: u32, + }, + /// A function which yields control to the host so that other tasks are able + /// to make progress, if any. + TaskYield { + /// If `true`, indicates the caller instance maybe reentered. + async_: bool, + }, + /// A function to drop a specified task which has completed. + SubtaskDrop, + /// A function to create a new `stream` handle of the specified type. + StreamNew { + /// The `stream` type to instantiate. + ty: u32, + }, + /// A function to read from a `stream` of the specified type. + StreamRead { + /// The `stream` type to expect. + ty: u32, + /// Any options (e.g. string encoding) to use when storing values to + /// memory. + options: Box<[CanonicalOption]>, + }, + /// A function to write to a `stream` of the specified type. + StreamWrite { + /// The `stream` type to expect. + ty: u32, + /// Any options (e.g. string encoding) to use when loading values from + /// memory. + options: Box<[CanonicalOption]>, + }, + /// A function to cancel an in-progress read from a `stream` of the + /// specified type. + StreamCancelRead { + /// The `stream` type to expect. + ty: u32, + /// If `false`, block until cancellation completes rather than return + /// `BLOCKED`. + async_: bool, + }, + /// A function to cancel an in-progress write to a `stream` of the specified + /// type. + StreamCancelWrite { + /// The `stream` type to expect. + ty: u32, + /// If `false`, block until cancellation completes rather than return + /// `BLOCKED`. + async_: bool, + }, + /// A function to close the readable end of a `stream` of the specified + /// type. + StreamCloseReadable { + /// The `stream` type to expect. + ty: u32, + }, + /// A function to close the writable end of a `stream` of the specified + /// type. + StreamCloseWritable { + /// The `stream` type to expect. + ty: u32, + }, + /// A function to create a new `future` handle of the specified type. + FutureNew { + /// The `future` type to instantiate. + ty: u32, + }, + /// A function to read from a `future` of the specified type. + FutureRead { + /// The `future` type to expect. + ty: u32, + /// Any options (e.g. string encoding) to use when storing values to + /// memory. + options: Box<[CanonicalOption]>, + }, + /// A function to write to a `future` of the specified type. + FutureWrite { + /// The `future` type to expect. + ty: u32, + /// Any options (e.g. string encoding) to use when loading values from + /// memory. + options: Box<[CanonicalOption]>, + }, + /// A function to cancel an in-progress read from a `future` of the + /// specified type. + FutureCancelRead { + /// The `future` type to expect. + ty: u32, + /// If `false`, block until cancellation completes rather than return + /// `BLOCKED`. + async_: bool, + }, + /// A function to cancel an in-progress write to a `future` of the specified + /// type. + FutureCancelWrite { + /// The `future` type to expect. + ty: u32, + /// If `false`, block until cancellation completes rather than return + /// `BLOCKED`. + async_: bool, + }, + /// A function to close the readable end of a `future` of the specified + /// type. + FutureCloseReadable { + /// The `future` type to expect. + ty: u32, + }, + /// A function to close the writable end of a `future` of the specified + /// type. + FutureCloseWritable { + /// The `future` type to expect. + ty: u32, + }, + /// A function to create a new `error` with a specified debug + /// message. + ErrorNew { + /// String encoding, memory, etc. to use when loading debug message. + options: Box<[CanonicalOption]>, + }, + /// A function to get the debug message for a specified `error`. + /// + /// Note that the debug message might not necessarily match what was passed + /// to `error.new`. + ErrorDebugMessage { + /// String encoding, memory, etc. to use when storing debug message. + options: Box<[CanonicalOption]>, + }, + /// A function to drop a specified `error`. + ErrorDrop, } /// A reader for the canonical section of a WebAssembly component. @@ -113,6 +273,79 @@ impl<'a> FromReader<'a> for CanonicalFunction { func_ty_index: reader.read()?, }, 0x06 => CanonicalFunction::ThreadHwConcurrency, + 0x08 => CanonicalFunction::TaskBackpressure, + 0x09 => CanonicalFunction::TaskReturn { + type_index: reader.read()?, + }, + 0x0a => CanonicalFunction::TaskWait { + async_: reader.read()?, + memory: reader.read()?, + }, + 0x0b => CanonicalFunction::TaskPoll { + async_: reader.read()?, + memory: reader.read()?, + }, + 0x0c => CanonicalFunction::TaskYield { + async_: reader.read()?, + }, + 0x0d => CanonicalFunction::SubtaskDrop, + 0x0e => CanonicalFunction::StreamNew { ty: reader.read()? }, + 0x0f => CanonicalFunction::StreamRead { + ty: reader.read()?, + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x10 => CanonicalFunction::StreamWrite { + ty: reader.read()?, + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x11 => CanonicalFunction::StreamCancelRead { + ty: reader.read()?, + async_: reader.read()?, + }, + 0x12 => CanonicalFunction::StreamCancelWrite { + ty: reader.read()?, + async_: reader.read()?, + }, + 0x13 => CanonicalFunction::StreamCloseReadable { ty: reader.read()? }, + 0x14 => CanonicalFunction::StreamCloseWritable { ty: reader.read()? }, + 0x15 => CanonicalFunction::FutureNew { ty: reader.read()? }, + 0x16 => CanonicalFunction::FutureRead { + ty: reader.read()?, + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x17 => CanonicalFunction::FutureWrite { + ty: reader.read()?, + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x18 => CanonicalFunction::FutureCancelRead { + ty: reader.read()?, + async_: reader.read()?, + }, + 0x19 => CanonicalFunction::FutureCancelWrite { + ty: reader.read()?, + async_: reader.read()?, + }, + 0x1a => CanonicalFunction::FutureCloseReadable { ty: reader.read()? }, + 0x1b => CanonicalFunction::FutureCloseWritable { ty: reader.read()? }, + 0x1c => CanonicalFunction::ErrorNew { + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x1d => CanonicalFunction::ErrorDebugMessage { + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x1e => CanonicalFunction::ErrorDrop, x => return reader.invalid_leading_byte(x, "canonical function"), }) } @@ -127,6 +360,8 @@ impl<'a> FromReader<'a> for CanonicalOption { 0x03 => CanonicalOption::Memory(reader.read_var_u32()?), 0x04 => CanonicalOption::Realloc(reader.read_var_u32()?), 0x05 => CanonicalOption::PostReturn(reader.read_var_u32()?), + 0x06 => CanonicalOption::Async, + 0x07 => CanonicalOption::Callback(reader.read_var_u32()?), x => return reader.invalid_leading_byte(x, "canonical option"), }) } diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs index 152c1c17d2..7beea33b7d 100644 --- a/crates/wasmparser/src/readers/component/types.rs +++ b/crates/wasmparser/src/readers/component/types.rs @@ -505,6 +505,12 @@ pub enum ComponentDefinedType<'a> { Own(u32), /// A borrowed handle to a resource. Borrow(u32), + /// A future type with the specified payload type. + Future(Option), + /// A stream type with the specified payload type. + Stream(ComponentValType), + /// The error type. + Error, } impl<'a> ComponentDefinedType<'a> { @@ -544,6 +550,9 @@ impl<'a> ComponentDefinedType<'a> { }, 0x69 => ComponentDefinedType::Own(reader.read()?), 0x68 => ComponentDefinedType::Borrow(reader.read()?), + 0x67 => ComponentDefinedType::Future(reader.read()?), + 0x66 => ComponentDefinedType::Stream(reader.read()?), + 0x65 => ComponentDefinedType::Error, x => return reader.invalid_leading_byte(x, "component defined type"), }) } diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index bb82a979a9..c4b9db51db 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1283,11 +1283,18 @@ impl Validator { options.into_vec(), types, offset, + features, ), crate::CanonicalFunction::Lower { func_index, options, - } => current.lower_function(func_index, options.into_vec(), types, offset), + } => current.lower_function( + func_index, + options.into_vec(), + types, + offset, + features, + ), crate::CanonicalFunction::ResourceNew { resource } => { current.resource_new(resource, types, offset) } @@ -1303,6 +1310,75 @@ impl Validator { crate::CanonicalFunction::ThreadHwConcurrency => { current.thread_hw_concurrency(types, offset, features) } + crate::CanonicalFunction::TaskBackpressure => { + current.task_backpressure(types, offset, features) + } + crate::CanonicalFunction::TaskReturn { type_index } => { + current.task_return(type_index, offset, features) + } + crate::CanonicalFunction::TaskWait { async_, memory } => { + current.task_wait(async_, memory, types, offset, features) + } + crate::CanonicalFunction::TaskPoll { async_, memory } => { + current.task_poll(async_, memory, types, offset, features) + } + crate::CanonicalFunction::TaskYield { async_ } => { + current.task_yield(async_, types, offset, features) + } + crate::CanonicalFunction::SubtaskDrop => { + current.subtask_drop(types, offset, features) + } + crate::CanonicalFunction::StreamNew { ty } => { + current.stream_new(ty, types, offset, features) + } + crate::CanonicalFunction::StreamRead { ty, options } => { + current.stream_read(ty, options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::StreamWrite { ty, options } => { + current.stream_write(ty, options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::StreamCancelRead { ty, async_ } => { + current.stream_cancel_read(ty, async_, types, offset, features) + } + crate::CanonicalFunction::StreamCancelWrite { ty, async_ } => { + current.stream_cancel_write(ty, async_, types, offset, features) + } + crate::CanonicalFunction::StreamCloseReadable { ty } => { + current.stream_close_readable(ty, types, offset, features) + } + crate::CanonicalFunction::StreamCloseWritable { ty } => { + current.stream_close_writable(ty, types, offset, features) + } + crate::CanonicalFunction::FutureNew { ty } => { + current.future_new(ty, types, offset, features) + } + crate::CanonicalFunction::FutureRead { ty, options } => { + current.future_read(ty, options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::FutureWrite { ty, options } => { + current.future_write(ty, options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::FutureCancelRead { ty, async_ } => { + current.future_cancel_read(ty, async_, types, offset, features) + } + crate::CanonicalFunction::FutureCancelWrite { ty, async_ } => { + current.future_cancel_write(ty, async_, types, offset, features) + } + crate::CanonicalFunction::FutureCloseReadable { ty } => { + current.future_close_readable(ty, types, offset, features) + } + crate::CanonicalFunction::FutureCloseWritable { ty } => { + current.future_close_writable(ty, types, offset, features) + } + crate::CanonicalFunction::ErrorNew { options } => { + current.error_new(options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::ErrorDebugMessage { options } => { + current.error_debug_message(options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::ErrorDrop => { + current.error_drop(types, offset, features) + } } }, ) diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 237e7994b4..3a1898be6a 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -19,9 +19,9 @@ use crate::prelude::*; use crate::validator::names::{ComponentName, ComponentNameKind, KebabStr, KebabString}; use crate::{ BinaryReaderError, CanonicalOption, ComponentExportName, ComponentExternalKind, - ComponentOuterAliasKind, ComponentTypeRef, CompositeInnerType, ExternalKind, FuncType, - GlobalType, InstantiationArgKind, MemoryType, PackedIndex, RefType, Result, SubType, TableType, - TypeBounds, ValType, WasmFeatures, + ComponentOuterAliasKind, ComponentTypeRef, CompositeInnerType, CompositeType, ExternalKind, + FuncType, GlobalType, InstantiationArgKind, MemoryType, PackedIndex, RecGroup, RefType, Result, + SubType, TableType, TypeBounds, ValType, WasmFeatures, }; use core::mem; @@ -721,7 +721,8 @@ impl ComponentState { // named. ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) - | ComponentDefinedType::Enum(_) => true, + | ComponentDefinedType::Enum(_) + | ComponentDefinedType::Error => true, // Referenced types of all these aggregates must all be // named. @@ -753,6 +754,12 @@ impl ComponentState { ComponentDefinedType::Own(id) | ComponentDefinedType::Borrow(id) => { set.contains(&ComponentAnyTypeId::from(*id)) } + + ComponentDefinedType::Future(ty) => ty + .as_ref() + .map(|ty| types.type_named_valtype(ty, set)) + .unwrap_or(true), + ComponentDefinedType::Stream(ty) => types.type_named_valtype(ty, set), } } @@ -949,14 +956,23 @@ impl ComponentState { options: Vec, types: &TypeList, offset: usize, + features: &WasmFeatures, ) -> Result<()> { let ty = self.function_type_at(type_index, types, offset)?; let core_ty = types[self.core_function_at(core_func_index, offset)?].unwrap_func(); // Lifting a function is for an export, so match the expected canonical ABI // export signature - let info = ty.lower(types, false); - self.check_options(Some(core_ty), &info, &options, types, offset)?; + let info = ty.lower(types, false, options.contains(&CanonicalOption::Async)); + self.check_options( + Some(core_ty), + &info, + &options, + types, + offset, + features, + true, + )?; if core_ty.params() != info.params.as_slice() { bail!( @@ -990,14 +1006,15 @@ impl ComponentState { options: Vec, types: &mut TypeAlloc, offset: usize, + features: &WasmFeatures, ) -> Result<()> { let ty = &types[self.function_at(func_index, offset)?]; // Lowering a function is for an import, so use a function type that matches // the expected canonical ABI import signature. - let info = ty.lower(types, true); + let info = ty.lower(types, true, options.contains(&CanonicalOption::Async)); - self.check_options(None, &info, &options, types, offset)?; + self.check_options(None, &info, &options, types, offset, features, true)?; let lowered_ty = SubType::func(info.into_func_type(), false); let id = types.intern_sub_type(lowered_ty, offset); @@ -1048,6 +1065,771 @@ impl ComponentState { Ok(()) } + pub fn task_backpressure( + &mut self, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`task.backpressure` requires the component model async feature" + ) + } + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn task_return( + &mut self, + type_index: u32, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`task.return` requires the component model async feature" + ) + } + + let id = self.type_id_at(type_index, offset)?; + self.core_funcs.push(id); + Ok(()) + } + + pub fn task_wait( + &mut self, + _async_: bool, + memory: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`task.wait` requires the component model async feature" + ) + } + + self.memory_at(memory, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn task_poll( + &mut self, + _async_: bool, + memory: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`task.poll` requires the component model async feature" + ) + } + + self.memory_at(memory, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn task_yield( + &mut self, + _async_: bool, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`task.yield` requires the component model async feature" + ) + } + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn subtask_drop( + &mut self, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`subtask.drop` requires the component model async feature" + ) + } + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_new( + &mut self, + ty: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.new` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_read( + &mut self, + ty: u32, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.read` requires the component model async feature" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = ComponentValType::Type(ty).contains_ptr(types); + self.check_options(None, &info, &options, types, offset, features, true)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new( + [ValType::I32; 3], + [ValType::I32], + )), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_write( + &mut self, + ty: u32, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.write` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = false; + self.check_options(None, &info, &options, types, offset, features, true)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new( + [ValType::I32; 3], + [ValType::I32], + )), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_cancel_read( + &mut self, + ty: u32, + _async_: bool, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.cancel-read` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_cancel_write( + &mut self, + ty: u32, + _async_: bool, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.cancel-write` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_close_readable( + &mut self, + ty: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.close-readable` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn stream_close_writable( + &mut self, + ty: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`stream.close-writable` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32; 2], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_new( + &mut self, + ty: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.new` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_read( + &mut self, + ty: u32, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.read` requires the component model async feature" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = ComponentValType::Type(ty).contains_ptr(types); + self.check_options(None, &info, &options, types, offset, features, true)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new( + [ValType::I32; 2], + [ValType::I32], + )), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_write( + &mut self, + ty: u32, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.write` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = false; + self.check_options(None, &info, &options, types, offset, features, true)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new( + [ValType::I32; 2], + [ValType::I32], + )), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_cancel_read( + &mut self, + ty: u32, + _async_: bool, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.cancel-read` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_cancel_write( + &mut self, + ty: u32, + _async_: bool, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.cancel-write` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [ValType::I32])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_close_readable( + &mut self, + ty: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.close-readable` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn future_close_writable( + &mut self, + ty: u32, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`future.drop-writeable` requires the component model async feature" + ) + } + + self.defined_type_at(ty, offset)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32; 2], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn error_new( + &mut self, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`error.new` requires the component model async feature" + ) + } + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = false; + self.check_options(None, &info, &options, types, offset, features, false)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new( + [ValType::I32; 2], + [ValType::I32], + )), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn error_debug_message( + &mut self, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`error.debug-message` requires the component model async feature" + ) + } + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = true; + self.check_options(None, &info, &options, types, offset, features, false)?; + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new( + [ValType::I32; 2], + [ValType::I32], + )), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + + pub fn error_drop( + &mut self, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`error.drop` requires the component model async feature" + ) + } + + let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit( + offset, + SubType { + is_final: true, + supertype_idx: None, + composite_type: CompositeType { + shared: false, + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])), + }, + }, + )); + let id = types[group_id].start; + self.core_funcs.push(id); + Ok(()) + } + fn check_local_resource(&self, idx: u32, types: &TypeList, offset: usize) -> Result { let resource = self.resource_at(idx, types, offset)?; match self @@ -1293,6 +2075,8 @@ impl ComponentState { options: &[CanonicalOption], types: &TypeList, offset: usize, + features: &WasmFeatures, + allow_async: bool, ) -> Result<()> { fn display(option: CanonicalOption) -> &'static str { match option { @@ -1302,6 +2086,8 @@ impl ComponentState { CanonicalOption::Memory(_) => "memory", CanonicalOption::Realloc(_) => "realloc", CanonicalOption::PostReturn(_) => "post-return", + CanonicalOption::Async => "async", + CanonicalOption::Callback(_) => "callback", } } @@ -1309,6 +2095,8 @@ impl ComponentState { let mut memory = None; let mut realloc = None; let mut post_return = None; + let mut async_ = false; + let mut callback = None; for option in options { match option { @@ -1390,9 +2178,62 @@ impl ComponentState { } } } + CanonicalOption::Async => { + if async_ { + return Err(BinaryReaderError::new( + "canonical option `async` is specified more than once", + offset, + )); + } else { + if !features.component_model_async() { + bail!( + offset, + "canonical option `async` requires the component model async feature" + ); + } + + async_ = true; + } + } + CanonicalOption::Callback(idx) => { + callback = match callback { + None => { + if core_ty.is_none() { + return Err(BinaryReaderError::new( + "canonical option `callback` cannot be specified for lowerings", + offset, + )); + } + + let ty = types[self.core_function_at(*idx, offset)?].unwrap_func(); + + if ty.params() != [ValType::I32; 4] && ty.params() != [ValType::I32] { + return Err(BinaryReaderError::new( + "canonical option `callback` uses a core function with an incorrect signature", + offset, + )); + } + Some(*idx) + } + Some(_) => { + return Err(BinaryReaderError::new( + "canonical option `callback` is specified more than once", + offset, + )) + } + } + } } } + if async_ && !allow_async { + bail!(offset, "async option not allowed here") + } + + if callback.is_some() && !async_ { + bail!(offset, "cannot specify callback without lifting async") + } + if info.requires_memory && memory.is_none() { return Err(BinaryReaderError::new( "canonical option `memory` is required", @@ -1817,7 +2658,7 @@ impl ComponentState { cx.entity_type(arg, expected, offset).with_context(|| { format!( "type mismatch for export `{name}` of module \ - instantiation argument `{module}`" + instantiation argument `{module}`" ) })?; } @@ -2598,6 +3439,14 @@ impl ComponentState { crate::ComponentDefinedType::Borrow(idx) => Ok(ComponentDefinedType::Borrow( self.resource_at(idx, types, offset)?, )), + crate::ComponentDefinedType::Future(ty) => Ok(ComponentDefinedType::Future( + ty.map(|ty| self.create_component_val_type(ty, offset)) + .transpose()?, + )), + crate::ComponentDefinedType::Stream(ty) => Ok(ComponentDefinedType::Stream( + self.create_component_val_type(ty, offset)?, + )), + crate::ComponentDefinedType::Error => Ok(ComponentDefinedType::Error), } } diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index ca42e6be15..b9a8390eb6 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -894,9 +894,19 @@ impl TypeData for ComponentFuncType { impl ComponentFuncType { /// Lowers the component function type to core parameter and result types for the /// canonical ABI. - pub(crate) fn lower(&self, types: &TypeList, is_lower: bool) -> LoweringInfo { + pub(crate) fn lower(&self, types: &TypeList, is_lower: bool, async_: bool) -> LoweringInfo { let mut info = LoweringInfo::default(); + if async_ && is_lower { + for _ in 0..2 { + info.params.push(ValType::I32); + } + info.results.push(ValType::I32); + info.requires_memory = true; + info.requires_realloc = self.results.iter().any(|(_, ty)| ty.contains_ptr(types)); + return info; + } + for (_, ty) in self.params.iter() { // Check to see if `ty` has a pointer somewhere in it, needed for // any type that transitively contains either a string or a list. @@ -930,27 +940,31 @@ impl ComponentFuncType { } } - for (_, ty) in self.results.iter() { - // Results of lowered functions that contains pointers must be - // allocated by the callee meaning that realloc is required. - // Results of lifted function are allocated by the guest which - // means that no realloc option is necessary. - if is_lower && !info.requires_realloc { - info.requires_realloc = ty.contains_ptr(types); - } + if async_ { + info.results.push(ValType::I32); + } else { + for (_, ty) in self.results.iter() { + // Results of lowered functions that contains pointers must be + // allocated by the callee meaning that realloc is required. + // Results of lifted function are allocated by the guest which + // means that no realloc option is necessary. + if is_lower && !info.requires_realloc { + info.requires_realloc = ty.contains_ptr(types); + } - if !ty.push_wasm_types(types, &mut info.results) { - // Too many results to return directly, either a retptr parameter will be used (import) - // or a single pointer will be returned (export) - info.results.clear(); - if is_lower { - info.params.max = MAX_LOWERED_TYPES; - assert!(info.params.push(ValType::I32)); - } else { - assert!(info.results.push(ValType::I32)); + if !ty.push_wasm_types(types, &mut info.results) { + // Too many results to return directly, either a retptr parameter will be used (import) + // or a single pointer will be returned (export) + info.results.clear(); + if is_lower { + info.params.max = MAX_LOWERED_TYPES; + assert!(info.params.push(ValType::I32)); + } else { + assert!(info.results.push(ValType::I32)); + } + info.requires_memory = true; + break; } - info.requires_memory = true; - break; } } @@ -1027,6 +1041,12 @@ pub enum ComponentDefinedType { Own(AliasableResourceId), /// The type is a borrowed handle to the specified resource. Borrow(AliasableResourceId), + /// A future type with the specified payload type. + Future(Option), + /// A stream type with the specified payload type. + Stream(ComponentValType), + /// The error type. + Error, } impl TypeData for ComponentDefinedType { @@ -1034,7 +1054,13 @@ impl TypeData for ComponentDefinedType { fn type_info(&self, types: &TypeList) -> TypeInfo { match self { - Self::Primitive(_) | Self::Flags(_) | Self::Enum(_) | Self::Own(_) => TypeInfo::new(), + Self::Primitive(_) + | Self::Flags(_) + | Self::Enum(_) + | Self::Own(_) + | Self::Future(_) + | Self::Stream(_) + | Self::Error => TypeInfo::new(), Self::Borrow(_) => TypeInfo::borrow(), Self::Record(r) => r.info, Self::Variant(v) => v.info, @@ -1062,7 +1088,13 @@ impl ComponentDefinedType { .any(|case| case.ty.map(|ty| ty.contains_ptr(types)).unwrap_or(false)), Self::List(_) => true, Self::Tuple(t) => t.types.iter().any(|ty| ty.contains_ptr(types)), - Self::Flags(_) | Self::Enum(_) | Self::Own(_) | Self::Borrow(_) => false, + Self::Flags(_) + | Self::Enum(_) + | Self::Own(_) + | Self::Borrow(_) + | Self::Future(_) + | Self::Stream(_) + | Self::Error => false, Self::Option(ty) => ty.contains_ptr(types), Self::Result { ok, err } => { ok.map(|ty| ty.contains_ptr(types)).unwrap_or(false) @@ -1091,7 +1123,12 @@ impl ComponentDefinedType { Self::Flags(names) => { (0..(names.len() + 31) / 32).all(|_| lowered_types.push(ValType::I32)) } - Self::Enum(_) | Self::Own(_) | Self::Borrow(_) => lowered_types.push(ValType::I32), + Self::Enum(_) + | Self::Own(_) + | Self::Borrow(_) + | Self::Future(_) + | Self::Stream(_) + | Self::Error => lowered_types.push(ValType::I32), Self::Option(ty) => { Self::push_variant_wasm_types([ty].into_iter(), types, lowered_types) } @@ -1159,6 +1196,9 @@ impl ComponentDefinedType { ComponentDefinedType::Result { .. } => "result", ComponentDefinedType::Own(_) => "own", ComponentDefinedType::Borrow(_) => "borrow", + ComponentDefinedType::Future(_) => "future", + ComponentDefinedType::Stream(_) => "stream", + ComponentDefinedType::Error => "error", } } } @@ -1873,7 +1913,8 @@ impl TypeAlloc { match &self[id] { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) - | ComponentDefinedType::Enum(_) => {} + | ComponentDefinedType::Enum(_) + | ComponentDefinedType::Error => {} ComponentDefinedType::Record(r) => { for ty in r.fields.values() { self.free_variables_valtype(ty, set); @@ -1891,7 +1932,9 @@ impl TypeAlloc { } } } - ComponentDefinedType::List(ty) | ComponentDefinedType::Option(ty) => { + ComponentDefinedType::List(ty) + | ComponentDefinedType::Option(ty) + | ComponentDefinedType::Stream(ty) => { self.free_variables_valtype(ty, set); } ComponentDefinedType::Result { ok, err } => { @@ -1905,6 +1948,11 @@ impl TypeAlloc { ComponentDefinedType::Own(id) | ComponentDefinedType::Borrow(id) => { set.insert(id.resource()); } + ComponentDefinedType::Future(ty) => { + if let Some(ty) = ty { + self.free_variables_valtype(ty, set); + } + } } } @@ -2005,7 +2053,7 @@ impl TypeAlloc { let ty = &self[id]; match ty { // Primitives are always considered named - ComponentDefinedType::Primitive(_) => true, + ComponentDefinedType::Primitive(_) | ComponentDefinedType::Error => true, // These structures are never allowed to be anonymous, so they // themselves must be named. @@ -2028,15 +2076,20 @@ impl TypeAlloc { .map(|t| self.type_named_valtype(t, set)) .unwrap_or(true) } - ComponentDefinedType::List(ty) | ComponentDefinedType::Option(ty) => { - self.type_named_valtype(ty, set) - } + ComponentDefinedType::List(ty) + | ComponentDefinedType::Option(ty) + | ComponentDefinedType::Stream(ty) => self.type_named_valtype(ty, set), // own/borrow themselves don't have to be named, but the resource // they refer to must be named. ComponentDefinedType::Own(id) | ComponentDefinedType::Borrow(id) => { set.contains(&ComponentAnyTypeId::from(*id)) } + + ComponentDefinedType::Future(ty) => ty + .as_ref() + .map(|ty| self.type_named_valtype(ty, set)) + .unwrap_or(true), } } @@ -2187,7 +2240,8 @@ where match &mut tmp { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) - | ComponentDefinedType::Enum(_) => {} + | ComponentDefinedType::Enum(_) + | ComponentDefinedType::Error => {} ComponentDefinedType::Record(r) => { for ty in r.fields.values_mut() { any_changed |= self.remap_valtype(ty, map); @@ -2205,7 +2259,9 @@ where } } } - ComponentDefinedType::List(ty) | ComponentDefinedType::Option(ty) => { + ComponentDefinedType::List(ty) + | ComponentDefinedType::Option(ty) + | ComponentDefinedType::Stream(ty) => { any_changed |= self.remap_valtype(ty, map); } ComponentDefinedType::Result { ok, err } => { @@ -2219,6 +2275,11 @@ where ComponentDefinedType::Own(id) | ComponentDefinedType::Borrow(id) => { any_changed |= self.remap_resource_id(id, map); } + ComponentDefinedType::Future(ty) => { + if let Some(ty) = ty { + any_changed |= self.remap_valtype(ty, map); + } + } } self.insert_if_any_changed(map, any_changed, id, tmp) } @@ -3146,6 +3207,21 @@ impl<'a> SubtypeCx<'a> { } (Own(_), b) => bail!(offset, "expected {}, found own", b.desc()), (Borrow(_), b) => bail!(offset, "expected {}, found borrow", b.desc()), + (Future(a), Future(b)) => match (a, b) { + (None, None) => Ok(()), + (Some(a), Some(b)) => self + .component_val_type(a, b, offset) + .with_context(|| "type mismatch in future"), + (None, Some(_)) => bail!(offset, "expected future type, but found none"), + (Some(_), None) => bail!(offset, "expected future type to not be present"), + }, + (Future(_), b) => bail!(offset, "expected {}, found future", b.desc()), + (Stream(a), Stream(b)) => self + .component_val_type(a, b, offset) + .with_context(|| "type mismatch in stream"), + (Stream(_), b) => bail!(offset, "expected {}, found stream", b.desc()), + (Error, Error) => Ok(()), + (Error, b) => bail!(offset, "expected {}, found error", b.desc()), } } diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 94bffb218f..4444f2a65a 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -227,6 +227,28 @@ impl Printer<'_, '_> { Ok(()) } + fn print_future_type(&mut self, state: &State, ty: Option) -> Result<()> { + self.start_group("future")?; + + if let Some(ty) = ty { + self.result.write_str(" ")?; + self.print_component_val_type(state, &ty)?; + } + + self.end_group()?; + + Ok(()) + } + + fn print_stream_type(&mut self, state: &State, ty: ComponentValType) -> Result<()> { + self.start_group("stream")?; + self.result.write_str(" ")?; + self.print_component_val_type(state, &ty)?; + self.end_group()?; + + Ok(()) + } + pub(crate) fn print_defined_type( &mut self, state: &State, @@ -252,6 +274,9 @@ impl Printer<'_, '_> { self.print_idx(&state.component.type_names, *idx)?; self.end_group()?; } + ComponentDefinedType::Future(ty) => self.print_future_type(state, *ty)?, + ComponentDefinedType::Stream(ty) => self.print_stream_type(state, *ty)?, + ComponentDefinedType::Error => self.result.write_str("error")?, } Ok(()) @@ -775,6 +800,12 @@ impl Printer<'_, '_> { self.print_idx(&state.core.func_names, *idx)?; self.end_group()?; } + CanonicalOption::Async => self.result.write_str("async")?, + CanonicalOption::Callback(idx) => { + self.start_group("callback ")?; + self.print_idx(&state.core.func_names, *idx)?; + self.end_group()?; + } } } Ok(()) @@ -885,6 +916,261 @@ impl Printer<'_, '_> { self.end_group()?; state.core.funcs += 1; } + CanonicalFunction::TaskBackpressure => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon task.backpressure")?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::TaskReturn { type_index } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon task.return ")?; + self.print_idx(&state.component.type_names, type_index)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::TaskWait { async_, memory } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon task.wait ")?; + if async_ { + self.result.write_str("async ")?; + } + self.print_idx(&state.component.type_names, memory)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::TaskPoll { async_, memory } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon task.poll ")?; + if async_ { + self.result.write_str("async ")?; + } + self.print_idx(&state.component.type_names, memory)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::TaskYield { async_ } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon task.yield")?; + if async_ { + self.result.write_str(" async")?; + } + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::SubtaskDrop => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon subtask.drop")?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamNew { ty } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.new ")?; + self.print_idx(&state.component.type_names, ty)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamRead { ty, options } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.read ")?; + self.print_idx(&state.component.type_names, ty)?; + self.result.write_str(" ")?; + self.print_canonical_options(state, &options)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamWrite { ty, options } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.write ")?; + self.print_idx(&state.component.type_names, ty)?; + self.result.write_str(" ")?; + self.print_canonical_options(state, &options)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamCancelRead { ty, async_ } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.cancel-read ")?; + self.print_idx(&state.component.type_names, ty)?; + if async_ { + self.result.write_str(" async")?; + } + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamCancelWrite { ty, async_ } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.cancel-write ")?; + self.print_idx(&state.component.type_names, ty)?; + if async_ { + self.result.write_str(" async")?; + } + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamCloseReadable { ty } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.close-readable ")?; + self.print_idx(&state.component.type_names, ty)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::StreamCloseWritable { ty } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon stream.close-writable ")?; + self.print_idx(&state.component.type_names, ty)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureNew { ty } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.new ")?; + self.print_idx(&state.component.type_names, ty)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureWrite { ty, options } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.write ")?; + self.print_idx(&state.component.type_names, ty)?; + self.result.write_str(" ")?; + self.print_canonical_options(state, &options)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureRead { ty, options } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.read ")?; + self.print_idx(&state.component.type_names, ty)?; + self.result.write_str(" ")?; + self.print_canonical_options(state, &options)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureCancelRead { ty, async_ } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.cancel-read ")?; + self.print_idx(&state.component.type_names, ty)?; + if async_ { + self.result.write_str(" async")?; + } + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureCancelWrite { ty, async_ } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.cancel-write ")?; + self.print_idx(&state.component.type_names, ty)?; + if async_ { + self.result.write_str(" async")?; + } + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureCloseReadable { ty } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.close-readable ")?; + self.print_idx(&state.component.type_names, ty)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::FutureCloseWritable { ty } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon future.close-writable ")?; + self.print_idx(&state.component.type_names, ty)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::ErrorNew { options } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon error.new ")?; + self.print_canonical_options(state, &options)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::ErrorDebugMessage { options } => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon error.debug-message ")?; + self.print_canonical_options(state, &options)?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } + CanonicalFunction::ErrorDrop => { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group("canon error.drop")?; + self.end_group()?; + self.end_group()?; + state.core.funcs += 1; + } } } diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 5fb025b269..553340622f 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -72,7 +72,7 @@ //! component model. use crate::metadata::{self, Bindgen, ModuleMetadata}; -use crate::validation::{Export, ExportMap, Import, ImportInstance, ImportMap}; +use crate::validation::{Export, ExportMap, Import, ImportInstance, ImportMap, PayloadInfo}; use crate::StringEncoding; use anyhow::{anyhow, bail, Context, Result}; use indexmap::{IndexMap, IndexSet}; @@ -84,8 +84,8 @@ use wasm_encoder::*; use wasmparser::Validator; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Function, FunctionKind, InterfaceId, LiveTypes, Resolve, Type, TypeDefKind, TypeId, TypeOwner, - WorldItem, WorldKey, + Docs, Function, FunctionKind, InterfaceId, LiveTypes, Resolve, Results, Stability, Type, + TypeDefKind, TypeId, TypeOwner, WorldItem, WorldKey, }; const INDIRECT_TABLE_NAME: &str = "$imports"; @@ -124,12 +124,13 @@ bitflags::bitflags! { /// A string encoding must be specified, which is always utf-8 for now /// today. const STRING_ENCODING = 1 << 2; + const ASYNC = 1 << 3; } } impl RequiredOptions { - fn for_import(resolve: &Resolve, func: &Function) -> RequiredOptions { - let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); + fn for_import(resolve: &Resolve, func: &Function, abi: AbiVariant) -> RequiredOptions { + let sig = resolve.wasm_signature(abi, func); let mut ret = RequiredOptions::empty(); // Lift the params and lower the results for imports ret.add_lift(TypeContents::for_types( @@ -143,11 +144,14 @@ impl RequiredOptions { if sig.retptr || sig.indirect_params { ret |= RequiredOptions::MEMORY; } + if abi == AbiVariant::GuestImportAsync { + ret |= RequiredOptions::ASYNC; + } ret } - fn for_export(resolve: &Resolve, func: &Function) -> RequiredOptions { - let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); + fn for_export(resolve: &Resolve, func: &Function, abi: AbiVariant) -> RequiredOptions { + let sig = resolve.wasm_signature(abi, func); let mut ret = RequiredOptions::empty(); // Lower the params and lift the results for exports ret.add_lower(TypeContents::for_types( @@ -165,6 +169,9 @@ impl RequiredOptions { ret |= RequiredOptions::REALLOC; } } + if abi == AbiVariant::GuestExportAsync { + ret |= RequiredOptions::ASYNC; + } ret } @@ -202,7 +209,7 @@ impl RequiredOptions { ) -> Result> { #[derive(Default)] struct Iter { - options: [Option; 3], + options: [Option; 5], current: usize, count: usize, } @@ -252,6 +259,10 @@ impl RequiredOptions { iter.push(encoding.into()); } + if self.contains(RequiredOptions::ASYNC) { + iter.push(CanonicalOption::Async); + } + Ok(iter) } } @@ -310,8 +321,9 @@ impl TypeContents { TypeDefKind::Enum(_) => Self::empty(), TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::LIST, TypeDefKind::Type(t) => Self::for_type(resolve, t), - TypeDefKind::Future(_) => todo!("encoding for future"), - TypeDefKind::Stream(_) => todo!("encoding for stream"), + TypeDefKind::Future(_) => Self::empty(), + TypeDefKind::Stream(_) => Self::empty(), + TypeDefKind::Error => Self::empty(), TypeDefKind::Unknown => unreachable!(), }, Type::String => Self::STRING, @@ -493,6 +505,10 @@ impl<'a> EncodingState<'a> { let ty = encoder.ty; // Don't encode empty instance types since they're not // meaningful to the runtime of the component anyway. + // + // TODO: Is this correct? What if another imported interface needs to + // alias a type exported by this interface but can't because we skipped + // encoding the import? if ty.is_empty() { return Ok(()); } @@ -615,6 +631,7 @@ impl<'a> EncodingState<'a> { CustomModule::Main => &self.info.encoder.main_module_exports, CustomModule::Adapter(name) => &self.info.encoder.adapters[name].required_exports, }; + if exports.is_empty() { return Ok(()); } @@ -623,18 +640,20 @@ impl<'a> EncodingState<'a> { let mut world_func_core_names = IndexMap::new(); for (core_name, export) in self.info.exports_for(module).iter() { match export { - Export::WorldFunc(name) => { + Export::WorldFunc(_, name, _) => { let prev = world_func_core_names.insert(name, core_name); assert!(prev.is_none()); } - Export::InterfaceFunc(id, name) => { + Export::InterfaceFunc(_, id, name, _) => { let prev = interface_func_core_names .entry(id) .or_insert(IndexMap::new()) .insert(name.as_str(), core_name); assert!(prev.is_none()); } - Export::WorldFuncPostReturn(..) + Export::WorldFuncCallback(..) + | Export::InterfaceFuncCallback(..) + | Export::WorldFuncPostReturn(..) | Export::InterfaceFuncPostReturn(..) | Export::ResourceDtor(..) | Export::Memory @@ -1000,8 +1019,15 @@ impl<'a> EncodingState<'a> { let metadata = self.info.module_metadata_for(module); let instance_index = self.instance_for(module); let core_func_index = self.core_alias_export(instance_index, core_name, ExportKind::Func); + let exports = self.info.exports_for(module); - let options = RequiredOptions::for_export(resolve, func); + let options = RequiredOptions::for_export( + resolve, + func, + exports + .abi(key, func) + .ok_or_else(|| anyhow!("no ABI found for {}", func.name))?, + ); let encoding = metadata .export_encodings @@ -1019,6 +1045,10 @@ impl<'a> EncodingState<'a> { let post_return = self.core_alias_export(instance_index, post_return, ExportKind::Func); options.push(CanonicalOption::PostReturn(post_return)); } + if let Some(callback) = exports.callback(key, func) { + let callback = self.core_alias_export(instance_index, callback, ExportKind::Func); + options.push(CanonicalOption::Callback(callback)); + } let func_index = self.component.lift_func(core_func_index, ty, options); Ok(func_index) } @@ -1168,6 +1198,8 @@ impl<'a> EncodingState<'a> { let table_index = self.core_alias_export(shim_instance_index, INDIRECT_TABLE_NAME, ExportKind::Table); + let resolve = &self.info.encoder.metadata.resolve; + let mut exports = Vec::new(); exports.push((INDIRECT_TABLE_NAME, ExportKind::Table, table_index)); @@ -1230,6 +1262,149 @@ impl<'a> EncodingState<'a> { ShimKind::ResourceDtor { module, export } => { self.core_alias_export(self.instance_for(*module), export, ExportKind::Func) } + + ShimKind::PayloadFunc { + for_module, + async_, + info, + kind, + } => { + let metadata = self.info.module_metadata_for(*for_module); + let exports = self.info.exports_for(*for_module); + let instance_index = self.instance_for(*for_module); + let (encoding, realloc) = if info.imported { + ( + metadata + .import_encodings + .get(resolve, &info.key, &info.function.name), + exports.import_realloc_for(info.interface, &info.function.name), + ) + } else { + ( + metadata + .export_encodings + .get(resolve, &info.key, &info.function.name), + exports.export_realloc_for(&info.key, &info.function), + ) + }; + let encoding = encoding.unwrap_or(StringEncoding::UTF8); + let realloc_index = realloc + .map(|name| self.core_alias_export(instance_index, name, ExportKind::Func)); + let options = |me: &mut Self, params: Vec, results: Vec| { + Ok::<_, anyhow::Error>( + (RequiredOptions::for_import( + resolve, + &Function { + name: String::new(), + kind: FunctionKind::Freestanding, + params: params + .into_iter() + .enumerate() + .map(|(i, v)| (format!("a{i}"), v)) + .collect(), + results: match &results[..] { + [] => Results::Named(Vec::new()), + [ty] => Results::Anon(*ty), + _ => unreachable!(), + }, + docs: Default::default(), + stability: Stability::Unknown, + }, + if *async_ { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + ) | RequiredOptions::MEMORY) + .into_iter(encoding, me.memory_index, realloc_index)? + .collect::>(), + ) + }; + let type_index = self.payload_type_index(info.ty)?; + + match kind { + PayloadFuncKind::FutureWrite => { + let TypeDefKind::Future(payload_type) = &resolve.types[info.ty].kind + else { + unreachable!() + }; + let options = options( + self, + if let Some(payload_type) = payload_type { + vec![*payload_type] + } else { + vec![] + }, + vec![], + )?; + self.component.future_write(type_index, options) + } + PayloadFuncKind::FutureRead => { + let TypeDefKind::Future(payload_type) = &resolve.types[info.ty].kind + else { + unreachable!() + }; + let options = options( + self, + vec![], + if let Some(payload_type) = payload_type { + vec![*payload_type] + } else { + vec![] + }, + )?; + self.component.future_read(type_index, options) + } + PayloadFuncKind::StreamWrite => { + let TypeDefKind::Stream(payload_type) = &resolve.types[info.ty].kind + else { + unreachable!() + }; + let options = options(self, vec![*payload_type], vec![])?; + self.component.stream_write(type_index, options) + } + PayloadFuncKind::StreamRead => { + let TypeDefKind::Stream(payload_type) = &resolve.types[info.ty].kind + else { + unreachable!() + }; + let options = options(self, vec![], vec![*payload_type])?; + self.component.stream_read(type_index, options) + } + } + } + + ShimKind::TaskWait { async_ } => self + .component + .task_wait(*async_, self.memory_index.unwrap()), + ShimKind::TaskPoll { async_ } => self + .component + .task_poll(*async_, self.memory_index.unwrap()), + ShimKind::ErrorNew { for_module } | ShimKind::ErrorDebugMessage { for_module } => { + let metadata = self.info.module_metadata_for(*for_module); + let exports = self.info.exports_for(*for_module); + let instance_index = self.instance_for(*for_module); + let encoding = metadata.general_purpose_encoding(); + let realloc = exports.general_purpose_realloc(); + let realloc_index = realloc + .map(|name| self.core_alias_export(instance_index, name, ExportKind::Func)); + + match &shim.kind { + ShimKind::ErrorNew { .. } => self.component.error_new( + (RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING) + .into_iter(encoding, self.memory_index, realloc_index)? + .collect::>(), + ), + ShimKind::ErrorDebugMessage { .. } => self.component.error_debug_message( + (RequiredOptions::MEMORY + | RequiredOptions::STRING_ENCODING + | RequiredOptions::REALLOC) + .into_iter(encoding, self.memory_index, realloc_index)? + .collect::>(), + ), + _ => unreachable!(), + } + } }; exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); @@ -1243,6 +1418,17 @@ impl<'a> EncodingState<'a> { Ok(()) } + fn payload_type_index(&mut self, ty: TypeId) -> Result { + let resolve = &self.info.encoder.metadata.resolve; + let ComponentValType::Type(type_index) = self + .root_import_type_encoder(None) + .encode_valtype(resolve, &Type::Id(ty))? + else { + unreachable!() + }; + Ok(type_index) + } + /// This is a helper function that will declare, in the component itself, /// all exported resources. /// @@ -1383,12 +1569,25 @@ impl<'a> EncodingState<'a> { for_module: CustomModule<'_>, module: &str, field: &str, - import: &Import, + import: &'a Import, ) -> Result<(ExportKind, u32)> { log::trace!("attempting to materialize import of `{module}::{field}` for {for_module:?}"); let resolve = &self.info.encoder.metadata.resolve; + let payload_indirect = |me: &mut Self, async_, info, kind| { + me.component.core_alias_export( + me.shim_instance_index.expect("shim should be instantiated"), + &shims.shims[&ShimKind::PayloadFunc { + for_module, + async_, + info, + kind, + }] + .name, + ExportKind::Func, + ) + }; let name_tmp; - let (key, name, interface_key) = match import { + let (key, name, interface_key, abi) = match import { // Main module dependencies on an adapter in use are done with an // indirection here, so load the shim function and use that. Import::AdapterExport(_) => { @@ -1459,10 +1658,176 @@ impl<'a> EncodingState<'a> { let ty = &resolve.types[*id]; let name = ty.name.as_ref().unwrap(); name_tmp = format!("{name}_drop"); - (key, &name_tmp, iface.map(|_| resolve.name_world_key(key))) + ( + key, + &name_tmp, + iface.map(|_| resolve.name_world_key(key)), + AbiVariant::GuestImport, + ) + } + Import::ExportedTaskReturn(function) => { + let signature = resolve.wasm_signature( + AbiVariant::GuestImport, + &Function { + name: String::new(), + kind: FunctionKind::Freestanding, + params: match &function.results { + Results::Named(params) => params.clone(), + Results::Anon(ty) => vec![("v".to_string(), *ty)], + }, + results: Results::Named(Vec::new()), + docs: Docs::default(), + stability: Stability::Unknown, + }, + ); + let (type_index, encoder) = self.component.core_type(); + encoder.core().function( + signature.params.into_iter().map(into_val_type), + signature.results.into_iter().map(into_val_type), + ); + + let index = self.component.task_return(type_index); + return Ok((ExportKind::Func, index)); + + fn into_val_type(ty: WasmType) -> ValType { + match ty { + WasmType::I32 | WasmType::Pointer | WasmType::Length => ValType::I32, + WasmType::I64 | WasmType::PointerOrI64 => ValType::I64, + WasmType::F32 => ValType::F32, + WasmType::F64 => ValType::F64, + } + } + } + Import::TaskBackpressure => { + let index = self.component.task_backpressure(); + return Ok((ExportKind::Func, index)); + } + Import::TaskWait { async_ } => { + let index = self.component.core_alias_export( + self.shim_instance_index + .expect("shim should be instantiated"), + &shims.shims[&ShimKind::TaskWait { async_: *async_ }].name, + ExportKind::Func, + ); + return Ok((ExportKind::Func, index)); + } + Import::TaskPoll { async_ } => { + let index = self.component.core_alias_export( + self.shim_instance_index + .expect("shim should be instantiated"), + &shims.shims[&ShimKind::TaskPoll { async_: *async_ }].name, + ExportKind::Func, + ); + return Ok((ExportKind::Func, index)); + } + Import::TaskYield { async_ } => { + let index = self.component.task_yield(*async_); + return Ok((ExportKind::Func, index)); + } + Import::SubtaskDrop => { + let index = self.component.subtask_drop(); + return Ok((ExportKind::Func, index)); + } + Import::StreamNew(info) => { + let ty = self.payload_type_index(info.ty)?; + let index = self.component.stream_new(ty); + return Ok((ExportKind::Func, index)); + } + Import::StreamRead { async_, info } => { + return Ok(( + ExportKind::Func, + payload_indirect(self, *async_, info, PayloadFuncKind::StreamRead), + )); + } + Import::StreamWrite { async_, info } => { + return Ok(( + ExportKind::Func, + payload_indirect(self, *async_, info, PayloadFuncKind::StreamWrite), + )); + } + Import::StreamCancelRead { ty, async_ } => { + let ty = self.payload_type_index(*ty)?; + let index = self.component.stream_cancel_read(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::StreamCancelWrite { ty, async_ } => { + let ty = self.payload_type_index(*ty)?; + let index = self.component.stream_cancel_write(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::StreamCloseReadable(ty) => { + let type_index = self.payload_type_index(*ty)?; + let index = self.component.stream_close_readable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::StreamCloseWritable(ty) => { + let type_index = self.payload_type_index(*ty)?; + let index = self.component.stream_close_writable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::FutureNew(info) => { + let ty = self.payload_type_index(info.ty)?; + let index = self.component.future_new(ty); + return Ok((ExportKind::Func, index)); + } + Import::FutureRead { async_, info } => { + return Ok(( + ExportKind::Func, + payload_indirect(self, *async_, info, PayloadFuncKind::FutureRead), + )); + } + Import::FutureWrite { async_, info } => { + return Ok(( + ExportKind::Func, + payload_indirect(self, *async_, info, PayloadFuncKind::FutureWrite), + )); + } + Import::FutureCancelRead { ty, async_ } => { + let ty = self.payload_type_index(*ty)?; + let index = self.component.future_cancel_read(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::FutureCancelWrite { ty, async_ } => { + let ty = self.payload_type_index(*ty)?; + let index = self.component.future_cancel_write(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::FutureCloseReadable(ty) => { + let type_index = self.payload_type_index(*ty)?; + let index = self.component.future_close_readable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::FutureCloseWritable(ty) => { + let type_index = self.payload_type_index(*ty)?; + let index = self.component.future_close_writable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::ErrorNew => { + let index = self.component.core_alias_export( + self.shim_instance_index + .expect("shim should be instantiated"), + &shims.shims[&ShimKind::ErrorNew { for_module }].name, + ExportKind::Func, + ); + return Ok((ExportKind::Func, index)); + } + Import::ErrorDebugMessage => { + let index = self.component.core_alias_export( + self.shim_instance_index + .expect("shim should be instantiated"), + &shims.shims[&ShimKind::ErrorDebugMessage { for_module }].name, + ExportKind::Func, + ); + return Ok((ExportKind::Func, index)); + } + Import::ErrorDrop => { + let index = self.component.error_drop(); + return Ok((ExportKind::Func, index)); + } + Import::WorldFunc(key, name, abi) => (key, name, None, *abi), + Import::InterfaceFunc(key, _, name, abi) => { + (key, name, Some(resolve.name_world_key(key)), *abi) } - Import::WorldFunc(key, name) => (key, name, None), - Import::InterfaceFunc(key, _, name) => (key, name, Some(resolve.name_world_key(key))), }; let import = &self.info.import_map[&interface_key]; @@ -1481,7 +1846,14 @@ impl<'a> EncodingState<'a> { } None => self.imported_funcs[name], }; - self.component.lower_func(func_index, []) + self.component.lower_func( + func_index, + if let AbiVariant::GuestImportAsync = abi { + vec![CanonicalOption::Async] + } else { + Vec::new() + }, + ) } // Indirect lowerings come from the shim that was previously @@ -1636,6 +2008,14 @@ struct Shim<'a> { sig: WasmSignature, } +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +enum PayloadFuncKind { + FutureWrite, + FutureRead, + StreamWrite, + StreamRead, +} + #[derive(Debug, Clone, Hash, Eq, PartialEq)] enum ShimKind<'a> { /// This shim is a late indirect lowering of an imported function in a @@ -1667,6 +2047,24 @@ enum ShimKind<'a> { /// The exported function name of this destructor in the core module. export: &'a str, }, + PayloadFunc { + for_module: CustomModule<'a>, + async_: bool, + info: &'a PayloadInfo, + kind: PayloadFuncKind, + }, + TaskWait { + async_: bool, + }, + TaskPoll { + async_: bool, + }, + ErrorNew { + for_module: CustomModule<'a>, + }, + ErrorDebugMessage { + for_module: CustomModule<'a>, + }, } /// Indicator for which module is being used for a lowering or where options @@ -1703,6 +2101,29 @@ impl<'a> Shims<'a> { let metadata = world.module_metadata_for(for_module); let resolve = &world.encoder.metadata.resolve; + let payload_push = + |me: &mut Self, module, async_, info: &'a PayloadInfo, kind, params, results| { + let debug_name = format!("{module}-{}", info.name); + let name = me.shims.len().to_string(); + me.push(Shim { + name, + debug_name, + options: RequiredOptions::empty(), + kind: ShimKind::PayloadFunc { + for_module, + async_, + info, + kind, + }, + sig: WasmSignature { + params, + results, + indirect_params: false, + retptr: false, + }, + }); + }; + for (module, field, import) in module_imports.imports() { let (key, name, interface_key) = match import { // These imports don't require shims, they can be satisfied @@ -1713,7 +2134,139 @@ impl<'a> Shims<'a> { | Import::Item(_) | Import::ExportedResourceDrop(..) | Import::ExportedResourceRep(..) - | Import::ExportedResourceNew(..) => continue, + | Import::ExportedResourceNew(..) + | Import::ErrorDrop + | Import::TaskBackpressure + | Import::TaskYield { .. } + | Import::SubtaskDrop + | Import::ExportedTaskReturn(..) + | Import::FutureNew(..) + | Import::StreamNew(..) + | Import::FutureCancelRead { .. } + | Import::FutureCancelWrite { .. } + | Import::FutureCloseWritable(..) + | Import::FutureCloseReadable(..) + | Import::StreamCancelRead { .. } + | Import::StreamCancelWrite { .. } + | Import::StreamCloseWritable(..) + | Import::StreamCloseReadable(..) => continue, + + Import::FutureWrite { async_, info } => { + payload_push( + self, + module, + *async_, + info, + PayloadFuncKind::FutureWrite, + vec![WasmType::I32; 2], + vec![WasmType::I32], + ); + continue; + } + Import::FutureRead { async_, info } => { + payload_push( + self, + module, + *async_, + info, + PayloadFuncKind::FutureRead, + vec![WasmType::I32; 2], + vec![WasmType::I32], + ); + continue; + } + Import::StreamWrite { async_, info } => { + payload_push( + self, + module, + *async_, + info, + PayloadFuncKind::StreamWrite, + vec![WasmType::I32; 3], + vec![WasmType::I32], + ); + continue; + } + Import::StreamRead { async_, info } => { + payload_push( + self, + module, + *async_, + info, + PayloadFuncKind::StreamRead, + vec![WasmType::I32; 3], + vec![WasmType::I32], + ); + continue; + } + + Import::TaskWait { async_ } => { + let name = self.shims.len().to_string(); + self.push(Shim { + name, + debug_name: "task-wait".to_string(), + options: RequiredOptions::empty(), + kind: ShimKind::TaskWait { async_: *async_ }, + sig: WasmSignature { + params: vec![WasmType::I32], + results: vec![WasmType::I32], + indirect_params: false, + retptr: false, + }, + }); + continue; + } + + Import::TaskPoll { async_ } => { + let name = self.shims.len().to_string(); + self.push(Shim { + name, + debug_name: "task-poll".to_string(), + options: RequiredOptions::empty(), + kind: ShimKind::TaskPoll { async_: *async_ }, + sig: WasmSignature { + params: vec![WasmType::I32], + results: vec![WasmType::I32], + indirect_params: false, + retptr: false, + }, + }); + continue; + } + + Import::ErrorNew => { + let name = self.shims.len().to_string(); + self.push(Shim { + name, + debug_name: "error-new".to_string(), + options: RequiredOptions::empty(), + kind: ShimKind::ErrorNew { for_module }, + sig: WasmSignature { + params: vec![WasmType::I32; 2], + results: vec![WasmType::I32], + indirect_params: false, + retptr: false, + }, + }); + continue; + } + + Import::ErrorDebugMessage => { + let name = self.shims.len().to_string(); + self.push(Shim { + name, + debug_name: "error-debug-message".to_string(), + options: RequiredOptions::empty(), + kind: ShimKind::ErrorDebugMessage { for_module }, + sig: WasmSignature { + params: vec![WasmType::I32; 2], + results: vec![], + indirect_params: false, + retptr: false, + }, + }); + continue; + } // Adapter imports into the main module must got through an // indirection, so that's registered here. @@ -1754,10 +2307,10 @@ impl<'a> Shims<'a> { // WIT-level functions may require an indirection, so yield some // metadata out of this `match` to the loop below to figure that // out. - Import::InterfaceFunc(key, _, name) => { + Import::InterfaceFunc(key, _, name, _) => { (key, name, Some(resolve.name_world_key(key))) } - Import::WorldFunc(key, name) => (key, name, None), + Import::WorldFunc(key, name, _) => (key, name, None), }; let interface = &world.import_map[&interface_key]; let (index, _, lowering) = interface.lowerings.get_full(name).unwrap(); diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 6a47c26cf8..0789ffecaf 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -153,8 +153,9 @@ pub trait ValtypeEncoder<'a> { ComponentValType::Type(index) } TypeDefKind::Type(ty) => self.encode_valtype(resolve, ty)?, - TypeDefKind::Future(_) => todo!("encoding for future type"), - TypeDefKind::Stream(_) => todo!("encoding for stream type"), + TypeDefKind::Future(ty) => self.encode_future(resolve, ty)?, + TypeDefKind::Stream(ty) => self.encode_stream(resolve, ty)?, + TypeDefKind::Error => self.encode_error()?, TypeDefKind::Unknown => unreachable!(), TypeDefKind::Resource => { let name = ty.name.as_ref().expect("resources must be named"); @@ -309,6 +310,30 @@ pub trait ValtypeEncoder<'a> { encoder.enum_type(enum_.cases.iter().map(|c| c.name.as_str())); Ok(ComponentValType::Type(index)) } + + fn encode_future( + &mut self, + resolve: &'a Resolve, + payload: &Option, + ) -> Result { + let ty = self.encode_optional_valtype(resolve, payload.as_ref())?; + let (index, encoder) = self.defined_type(); + encoder.future(ty); + Ok(ComponentValType::Type(index)) + } + + fn encode_stream(&mut self, resolve: &'a Resolve, payload: &Type) -> Result { + let ty = self.encode_valtype(resolve, payload)?; + let (index, encoder) = self.defined_type(); + encoder.stream(ty); + Ok(ComponentValType::Type(index)) + } + + fn encode_error(&mut self) -> Result { + let (index, encoder) = self.defined_type(); + encoder.error(); + Ok(ComponentValType::Type(index)) + } } pub struct RootTypeEncoder<'state, 'a> { diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index acf126a174..1be8e86f8e 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -215,19 +215,19 @@ impl<'a> ComponentWorld<'a> { .chain(self.info.imports.imports()) { match import { - Import::WorldFunc(_, name) => { + Import::WorldFunc(_, name, abi) => { required .interface_funcs .entry(None) .or_default() - .insert(name); + .insert((name, *abi)); } - Import::InterfaceFunc(_, id, name) => { + Import::InterfaceFunc(_, id, name, abi) => { required .interface_funcs .entry(Some(*id)) .or_default() - .insert(name); + .insert((name, *abi)); } Import::ImportedResourceDrop(_, _, id) => { required.resource_drops.insert(*id); @@ -414,22 +414,27 @@ impl<'a> ComponentWorld<'a> { #[derive(Default)] struct Required<'a> { - interface_funcs: IndexMap, IndexSet<&'a str>>, + interface_funcs: IndexMap, IndexSet<(&'a str, AbiVariant)>>, resource_drops: IndexSet, } impl ImportedInterface { fn add_func(&mut self, required: &Required<'_>, resolve: &Resolve, func: &Function) { - match required.interface_funcs.get(&self.interface) { - Some(set) if set.contains(func.name.as_str()) => {} + let abi = match required.interface_funcs.get(&self.interface) { + Some(set) if set.contains(&(func.name.as_str(), AbiVariant::GuestImport)) => { + AbiVariant::GuestImport + } + Some(set) if set.contains(&(func.name.as_str(), AbiVariant::GuestImportAsync)) => { + AbiVariant::GuestImportAsync + } _ => return, - } + }; log::trace!("add func {}", func.name); - let options = RequiredOptions::for_import(resolve, func); + let options = RequiredOptions::for_import(resolve, func, abi); let lowering = if options.is_empty() { Lowering::Direct } else { - let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); + let sig = resolve.wasm_signature(abi, func); Lowering::Indirect { sig, options } }; diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 7eaf5a68d4..e835d1f84b 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -116,6 +116,8 @@ pub struct ModuleMetadata { /// Per-function options exported from the core wasm module, currently only /// related to string encoding. pub export_encodings: EncodingMap, + + general_purpose_encoding: StringEncoding, } /// Internal map that keeps track of encodings for various world imports and @@ -402,6 +404,7 @@ impl Bindgen { ModuleMetadata { import_encodings, export_encodings, + general_purpose_encoding, }, producers, } = other; @@ -418,6 +421,13 @@ impl Bindgen { self.metadata.import_encodings.merge(import_encodings)?; self.metadata.export_encodings.merge(export_encodings)?; + if self.metadata.general_purpose_encoding != general_purpose_encoding { + bail!( + "string encoding mismatch: {:?} vs {:?}", + self.metadata.general_purpose_encoding, + general_purpose_encoding + ); + } if let Some(producers) = producers { if let Some(mine) = &mut self.producers { mine.merge(&producers); @@ -442,6 +452,14 @@ impl ModuleMetadata { ret.import_encodings .insert_all(resolve, &world.imports, encoding); + ret.general_purpose_encoding = encoding; + ret } + + /// String encoding to use for passing strings to and from built-in + /// functions such as `error.new`, `error.debug-string`, etc. + pub fn general_purpose_encoding(&self) -> StringEncoding { + self.general_purpose_encoding + } } diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 13e03ce0c6..fca1faf917 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -540,6 +540,7 @@ impl WitPrinter { TypeDefKind::Stream(_) => { todo!("document has an unnamed stream type") } + TypeDefKind::Error => self.output.push_str("error"), TypeDefKind::Unknown => unreachable!(), } } @@ -695,8 +696,9 @@ impl WitPrinter { } None => bail!("unnamed type in document"), }, - TypeDefKind::Future(_) => todo!("declare future"), - TypeDefKind::Stream(_) => todo!("declare stream"), + TypeDefKind::Future(_) => panic!("no need to declare futures"), + TypeDefKind::Stream(_) => panic!("no need to declare streams"), + TypeDefKind::Error => panic!("no need to declare errors"), TypeDefKind::Unknown => unreachable!(), } } @@ -987,6 +989,7 @@ fn is_keyword(name: &str) -> bool { | "with" | "include" | "constructor" + | "error" ) } diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index 21598710f1..a4ea755079 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -2,6 +2,7 @@ use crate::encoding::{Instance, Item, LibraryInfo, MainOrAdapter}; use crate::ComponentEncoder; use anyhow::{bail, Context, Result}; use indexmap::{map::Entry, IndexMap, IndexSet}; +use std::hash::{Hash, Hasher}; use std::mem; use wasm_encoder::ExportKind; use wasmparser::names::{ComponentName, ComponentNameKind}; @@ -11,7 +12,8 @@ use wasmparser::{ }; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Function, InterfaceId, PackageName, Resolve, TypeDefKind, TypeId, WorldId, WorldItem, WorldKey, + Function, InterfaceId, PackageName, Resolve, TypeDefKind, TypeId, World, WorldId, WorldItem, + WorldKey, }; fn wasm_sig_to_func_type(signature: WasmSignature) -> FuncType { @@ -39,6 +41,7 @@ fn wasm_sig_to_func_type(signature: WasmSignature) -> FuncType { /// module. Each of these specialized types contains "connection" information /// between a module's imports/exports and the WIT or component-level constructs /// they correspond to. + #[derive(Default)] pub struct ValidatedModule { /// Information about a module's imports. @@ -114,6 +117,26 @@ pub enum ImportInstance { Names(IndexMap), } +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct PayloadInfo { + pub name: String, + pub ty: TypeId, + pub function: Function, + pub key: WorldKey, + pub interface: Option, + pub imported: bool, +} + +impl Hash for PayloadInfo { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.ty.hash(state); + self.key.hash(state); + self.interface.hash(state); + self.imported.hash(state); + } +} + /// The different kinds of items that a module or an adapter can import. /// /// This is intended to be an exhaustive definition of what can be imported into @@ -122,14 +145,14 @@ pub enum ImportInstance { pub enum Import { /// A top-level world function, with the name provided here, is imported /// into the module. - WorldFunc(WorldKey, String), + WorldFunc(WorldKey, String, AbiVariant), /// An interface's function is imported into the module. /// /// The `WorldKey` here is the name of the interface in the world in /// question. The `InterfaceId` is the interface that was imported from and /// `String` is the WIT name of the function. - InterfaceFunc(WorldKey, InterfaceId, String), + InterfaceFunc(WorldKey, InterfaceId, String, AbiVariant), /// An imported resource's destructor is imported. /// @@ -172,7 +195,10 @@ pub enum Import { MainModuleMemory, /// An adapter is importing an arbitrary item from the main module. - MainModuleExport { name: String, kind: ExportKind }, + MainModuleExport { + name: String, + kind: ExportKind, + }, /// An arbitrary item from either the main module or an adapter is being /// imported. @@ -180,13 +206,67 @@ pub enum Import { /// (should probably subsume `MainModule*` and maybe `AdapterExport` above /// one day. Item(Item), + + ExportedTaskReturn(Function), + TaskBackpressure, + TaskWait { + async_: bool, + }, + TaskPoll { + async_: bool, + }, + TaskYield { + async_: bool, + }, + SubtaskDrop, + StreamNew(PayloadInfo), + StreamRead { + async_: bool, + info: PayloadInfo, + }, + StreamWrite { + async_: bool, + info: PayloadInfo, + }, + StreamCancelRead { + ty: TypeId, + async_: bool, + }, + StreamCancelWrite { + ty: TypeId, + async_: bool, + }, + StreamCloseReadable(TypeId), + StreamCloseWritable(TypeId), + FutureNew(PayloadInfo), + FutureRead { + async_: bool, + info: PayloadInfo, + }, + FutureWrite { + async_: bool, + info: PayloadInfo, + }, + FutureCancelRead { + ty: TypeId, + async_: bool, + }, + FutureCancelWrite { + ty: TypeId, + async_: bool, + }, + FutureCloseReadable(TypeId), + FutureCloseWritable(TypeId), + ErrorNew, + ErrorDebugMessage, + ErrorDrop, } impl ImportMap { /// Returns whether the top-level world function `func` is imported. pub fn uses_toplevel_func(&self, func: &str) -> bool { self.imports().any(|(_, _, item)| match item { - Import::WorldFunc(_, name) => func == name, + Import::WorldFunc(_, name, _) => func == name, _ => false, }) } @@ -194,7 +274,7 @@ impl ImportMap { /// Returns whether the interface function specified is imported. pub fn uses_interface_func(&self, interface: InterfaceId, func: &str) -> bool { self.imports().any(|(_, _, import)| match import { - Import::InterfaceFunc(_, id, name) => *id == interface && name == func, + Import::InterfaceFunc(_, id, name, _) => *id == interface && name == func, _ => false, }) } @@ -328,11 +408,94 @@ impl ImportMap { let world_id = encoder.metadata.world; let world = &resolve.worlds[world_id]; + if let Some(import) = names.payload_import(module, name, resolve, world, ty)? { + return Ok(import); + } + + let async_import = |interface: Option<(WorldKey, InterfaceId)>| { + Ok::<_, anyhow::Error>(if let Some(function_name) = names.task_return_name(name) { + let interface_id = interface.as_ref().map(|(_, id)| *id); + let func = get_function(resolve, world, function_name, interface_id, true)?; + // Note that we can't statically validate the type signature of + // a `task.return` built-in since we can't know which export + // it's associated with in general. Instead, the host will + // compare it with the expected type at runtime and trap if + // necessary. + Some(Import::ExportedTaskReturn(func)) + } else { + None + }) + }; + + let (abi, name) = if let Some(name) = names.async_name(name) { + (AbiVariant::GuestImportAsync, name) + } else { + (AbiVariant::GuestImport, name) + }; + if module == names.import_root() { + if Some(name) == names.error_drop() { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ErrorDrop); + } + + if Some(name) == names.task_backpressure() { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::TaskBackpressure); + } + + if Some(name) == names.task_wait() { + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::TaskWait { + async_: abi == AbiVariant::GuestImportAsync, + }); + } + + if Some(name) == names.task_poll() { + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::TaskPoll { + async_: abi == AbiVariant::GuestImportAsync, + }); + } + + if Some(name) == names.task_yield() { + let expected = FuncType::new([], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::TaskYield { + async_: abi == AbiVariant::GuestImportAsync, + }); + } + + if Some(name) == names.subtask_drop() { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::SubtaskDrop); + } + + if Some(name) == names.error_new() { + let expected = FuncType::new([ValType::I32; 2], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ErrorNew); + } + + if Some(name) == names.error_debug_message() { + let expected = FuncType::new([ValType::I32; 2], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ErrorDebugMessage); + } + + if let Some(import) = async_import(None)? { + return Ok(import); + } + let key = WorldKey::Name(name.to_string()); if let Some(WorldItem::Function(func)) = world.imports.get(&key) { - validate_func(resolve, ty, func, AbiVariant::GuestImport)?; - return Ok(Import::WorldFunc(key, func.name.clone())); + validate_func(resolve, ty, func, abi)?; + return Ok(Import::WorldFunc(key, func.name.clone(), abi)); } let get_resource = resource_test_for_world(resolve, world_id); @@ -356,6 +519,14 @@ impl ImportMap { }; if let Some(interface) = interface.strip_prefix(names.import_exported_intrinsic_prefix()) { + if let Some(import) = async_import(Some(names.module_to_interface( + interface, + resolve, + &world.exports, + )?))? { + return Ok(import); + } + let (key, id) = names.module_to_interface(interface, resolve, &world.exports)?; let get_resource = resource_test_for_interface(resolve, id); @@ -387,11 +558,11 @@ impl ImportMap { let interface = &resolve.interfaces[id]; let get_resource = resource_test_for_interface(resolve, id); if let Some(f) = interface.functions.get(name) { - validate_func(resolve, ty, f, AbiVariant::GuestImport).with_context(|| { + validate_func(resolve, ty, f, abi).with_context(|| { let name = resolve.name_world_key(&key); format!("failed to validate import interface `{name}`") })?; - return Ok(Import::InterfaceFunc(key, id, f.name.clone())); + return Ok(Import::InterfaceFunc(key, id, f.name.clone(), abi)); } else if let Some(resource) = names.resource_drop_name(name) { if let Some(resource) = get_resource(resource) { let expected = FuncType::new([ValType::I32], []); @@ -489,13 +660,13 @@ pub struct ExportMap { pub enum Export { /// An export of a top-level function of a world, where the world function /// is named here. - WorldFunc(String), + WorldFunc(WorldKey, String, AbiVariant), /// A post-return for a top-level function of a world. WorldFuncPostReturn(WorldKey), /// An export of a function in an interface. - InterfaceFunc(InterfaceId, String), + InterfaceFunc(WorldKey, InterfaceId, String, AbiVariant), /// A post-return for the above function. InterfaceFuncPostReturn(WorldKey, String), @@ -520,6 +691,10 @@ pub enum Export { /// `cabi_realloc_adapter` ReallocForAdapter, + + WorldFuncCallback(WorldKey), + + InterfaceFuncCallback(WorldKey, String), } impl ExportMap { @@ -569,6 +744,7 @@ impl ExportMap { None if encoder.reject_legacy_names => return Ok(None), None => (export.name, LEGACY), }; + if let Some(export) = self .classify_component_export(names, name, &export, encoder, exports, types) .with_context(|| format!("failed to classify export `{}`", export.name))? @@ -613,18 +789,30 @@ impl ExportMap { return Ok(Some(Export::Initialize)); } + let full_name = name; + let (abi, name) = if let Some(name) = names.async_name(name) { + (AbiVariant::GuestExportAsync, name) + } else { + (AbiVariant::GuestExport, name) + }; + // Try to match this to a known WIT export that `exports` allows. if let Some((key, id, f)) = names.match_wit_export(name, resolve, world, exports) { - validate_func(resolve, ty, f, AbiVariant::GuestExport).with_context(|| { + validate_func(resolve, ty, f, abi).with_context(|| { let key = resolve.name_world_key(key); format!("failed to validate export for `{key}`") })?; match id { Some(id) => { - return Ok(Some(Export::InterfaceFunc(id, f.name.clone()))); + return Ok(Some(Export::InterfaceFunc( + key.clone(), + id, + f.name.clone(), + abi, + ))); } None => { - return Ok(Some(Export::WorldFunc(f.name.clone()))); + return Ok(Some(Export::WorldFunc(key.clone(), f.name.clone(), abi))); } } } @@ -650,18 +838,33 @@ impl ExportMap { } } + if let Some(suffix) = names.callback_name(full_name) { + if let Some((key, id, f)) = names.match_wit_export(suffix, resolve, world, exports) { + validate_func_sig( + full_name, + &FuncType::new([ValType::I32; 4], [ValType::I32]), + ty, + )?; + return Ok(Some(if id.is_some() { + Export::InterfaceFuncCallback(key.clone(), f.name.clone()) + } else { + Export::WorldFuncCallback(key.clone()) + })); + } + } + // And, finally, see if it matches a known destructor. if let Some(dtor) = names.match_wit_resource_dtor(name, resolve, world, exports) { let expected = FuncType::new([ValType::I32], []); - validate_func_sig(export.name, &expected, ty)?; + validate_func_sig(full_name, &expected, ty)?; return Ok(Some(Export::ResourceDtor(dtor))); } Ok(None) } - /// Returns the name of the post-return export, if any, for the `interface` - /// and `func` combo. + /// Returns the name of the post-return export, if any, for the `key` and + /// `func` combo. pub fn post_return(&self, key: &WorldKey, func: &Function) -> Option<&str> { self.find(|m| match m { Export::WorldFuncPostReturn(k) => k == key, @@ -670,6 +873,24 @@ impl ExportMap { }) } + /// Returns the name of the async callback export, if any, for the `key` and + /// `func` combo. + pub fn callback(&self, key: &WorldKey, func: &Function) -> Option<&str> { + self.find(|m| match m { + Export::WorldFuncCallback(k) => k == key, + Export::InterfaceFuncCallback(k, f) => k == key && func.name == *f, + _ => false, + }) + } + + pub fn abi(&self, key: &WorldKey, func: &Function) -> Option { + self.names.values().find_map(|m| match m { + Export::WorldFunc(k, f, abi) if k == key && func.name == *f => Some(*abi), + Export::InterfaceFunc(k, _, f, abi) if k == key && func.name == *f => Some(*abi), + _ => None, + }) + } + /// Returns the realloc that the exported function `interface` and `func` /// are using. pub fn export_realloc_for(&self, key: &WorldKey, func: &Function) -> Option<&str> { @@ -706,7 +927,9 @@ impl ExportMap { self.general_purpose_realloc() } - fn general_purpose_realloc(&self) -> Option<&str> { + /// Returns the realloc to use when calling built-in functions such as + /// `error.new`, `error.debug-string`, etc. + pub fn general_purpose_realloc(&self) -> Option<&str> { self.find(|m| matches!(m, Export::GeneralPurposeRealloc)) } @@ -759,7 +982,7 @@ impl ExportMap { for export in exports { let require_interface_func = |interface: InterfaceId, name: &str| -> Result<()> { let result = self.find(|e| match e { - Export::InterfaceFunc(id, s) => interface == *id && name == s, + Export::InterfaceFunc(_, id, s, _) => interface == *id && name == s, _ => false, }); if result.is_some() { @@ -771,7 +994,7 @@ impl ExportMap { }; let require_world_func = |name: &str| -> Result<()> { let result = self.find(|e| match e { - Export::WorldFunc(s) => name == s, + Export::WorldFunc(_, s, _) => name == s, _ => false, }); if result.is_some() { @@ -828,6 +1051,25 @@ trait NameMangling { fn resource_drop_name<'a>(&self, s: &'a str) -> Option<&'a str>; fn resource_new_name<'a>(&self, s: &'a str) -> Option<&'a str>; fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn task_return_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn task_backpressure(&self) -> Option<&str>; + fn task_wait(&self) -> Option<&str>; + fn task_poll(&self) -> Option<&str>; + fn task_yield(&self) -> Option<&str>; + fn subtask_drop(&self) -> Option<&str>; + fn callback_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn async_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn error_new(&self) -> Option<&str>; + fn error_debug_message(&self) -> Option<&str>; + fn error_drop(&self) -> Option<&str>; + fn payload_import( + &self, + module: &str, + name: &str, + resolve: &Resolve, + world: &World, + ty: &FuncType, + ) -> Result>; fn module_to_interface( &self, module: &str, @@ -885,6 +1127,53 @@ impl NameMangling for Standard { fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str> { s.strip_suffix("_rep") } + fn task_return_name<'a>(&self, s: &'a str) -> Option<&'a str> { + _ = s; + None + } + fn task_backpressure(&self) -> Option<&str> { + None + } + fn task_wait(&self) -> Option<&str> { + None + } + fn task_poll(&self) -> Option<&str> { + None + } + fn task_yield(&self) -> Option<&str> { + None + } + fn subtask_drop(&self) -> Option<&str> { + None + } + fn callback_name<'a>(&self, s: &'a str) -> Option<&'a str> { + _ = s; + None + } + fn async_name<'a>(&self, s: &'a str) -> Option<&'a str> { + _ = s; + None + } + fn error_new(&self) -> Option<&str> { + None + } + fn error_debug_message(&self) -> Option<&str> { + None + } + fn error_drop(&self) -> Option<&str> { + None + } + fn payload_import( + &self, + module: &str, + name: &str, + resolve: &Resolve, + world: &World, + ty: &FuncType, + ) -> Result> { + _ = (module, name, resolve, world, ty); + Ok(None) + } fn module_to_interface( &self, interface: &str, @@ -1020,6 +1309,216 @@ impl NameMangling for Legacy { fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str> { s.strip_prefix("[resource-rep]") } + fn task_return_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[task-return]") + } + fn task_backpressure(&self) -> Option<&str> { + Some("[task-backpressure]") + } + fn task_wait(&self) -> Option<&str> { + Some("[task-wait]") + } + fn task_poll(&self) -> Option<&str> { + Some("[task-poll]") + } + fn task_yield(&self) -> Option<&str> { + Some("[task-yield]") + } + fn subtask_drop(&self) -> Option<&str> { + Some("[subtask-drop]") + } + fn callback_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[callback][async]") + } + fn async_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[async]") + } + fn error_new(&self) -> Option<&str> { + Some("[error-new]") + } + fn error_debug_message(&self) -> Option<&str> { + Some("[error-debug-message]") + } + fn error_drop(&self) -> Option<&str> { + Some("[error-drop]") + } + fn payload_import( + &self, + module: &str, + name: &str, + resolve: &Resolve, + world: &World, + ty: &FuncType, + ) -> Result> { + Ok( + if let Some((suffix, imported)) = module + .strip_prefix("[import-payload]") + .map(|v| (v, true)) + .or_else(|| name.strip_prefix("[export-payload]").map(|v| (v, false))) + { + let (key, interface) = if suffix == self.import_root() { + (WorldKey::Name(name.to_string()), None) + } else { + let (key, id) = self.module_to_interface( + suffix, + resolve, + if imported { + &world.imports + } else { + &world.exports + }, + )?; + (key, Some(id)) + }; + + let orig_name = name; + + let (name, async_) = if let Some(name) = self.async_name(name) { + (name, true) + } else { + (name, false) + }; + + let info = |payload_key| { + let (function, ty) = get_future_or_stream_type( + resolve, + world, + &payload_key, + interface, + imported, + )?; + Ok::<_, anyhow::Error>(PayloadInfo { + name: orig_name.to_string(), + ty, + function, + key: key.clone(), + interface, + imported, + }) + }; + + Some( + if let Some(key) = match_payload_prefix(name, "[future-new-") { + if async_ { + bail!("async `future.new` calls not supported"); + } + validate_func_sig(name, &FuncType::new([], [ValType::I32]), ty)?; + Import::FutureNew(info(key)?) + } else if let Some(key) = match_payload_prefix(name, "[future-write-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32; 2], [ValType::I32]), + ty, + )?; + Import::FutureWrite { + async_, + info: info(key)?, + } + } else if let Some(key) = match_payload_prefix(name, "[future-read-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32; 2], [ValType::I32]), + ty, + )?; + Import::FutureRead { + async_, + info: info(key)?, + } + } else if let Some(key) = match_payload_prefix(name, "[future-cancel-write-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32], [ValType::I32]), + ty, + )?; + Import::FutureCancelWrite { + async_, + ty: info(key)?.ty, + } + } else if let Some(key) = match_payload_prefix(name, "[future-cancel-read-") { + validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; + Import::FutureCancelRead { + async_, + ty: info(key)?.ty, + } + } else if let Some(key) = match_payload_prefix(name, "[future-close-writable-") + { + if async_ { + bail!("async `future.close-writable` calls not supported"); + } + validate_func_sig(name, &FuncType::new([ValType::I32; 2], []), ty)?; + Import::FutureCloseWritable(info(key)?.ty) + } else if let Some(key) = match_payload_prefix(name, "[future-close-readable-") + { + if async_ { + bail!("async `future.close-readable` calls not supported"); + } + validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; + Import::FutureCloseReadable(info(key)?.ty) + } else if let Some(key) = match_payload_prefix(name, "[stream-new-") { + if async_ { + bail!("async `stream.new` calls not supported"); + } + validate_func_sig(name, &FuncType::new([], [ValType::I32]), ty)?; + Import::StreamNew(info(key)?) + } else if let Some(key) = match_payload_prefix(name, "[stream-write-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32; 3], [ValType::I32]), + ty, + )?; + Import::StreamWrite { + async_, + info: info(key)?, + } + } else if let Some(key) = match_payload_prefix(name, "[stream-read-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32; 3], [ValType::I32]), + ty, + )?; + Import::StreamRead { + async_, + info: info(key)?, + } + } else if let Some(key) = match_payload_prefix(name, "[stream-cancel-write-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32], [ValType::I32]), + ty, + )?; + Import::StreamCancelWrite { + async_, + ty: info(key)?.ty, + } + } else if let Some(key) = match_payload_prefix(name, "[stream-cancel-read-") { + validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; + Import::StreamCancelRead { + async_, + ty: info(key)?.ty, + } + } else if let Some(key) = match_payload_prefix(name, "[stream-close-writable-") + { + if async_ { + bail!("async `stream.close-writable` calls not supported"); + } + validate_func_sig(name, &FuncType::new([ValType::I32; 2], []), ty)?; + Import::StreamCloseWritable(info(key)?.ty) + } else if let Some(key) = match_payload_prefix(name, "[stream-close-readable-") + { + if async_ { + bail!("async `stream.close-readable` calls not supported"); + } + validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; + Import::StreamCloseReadable(info(key)?.ty) + } else { + bail!("unrecognized payload import: {name}"); + }, + ) + } else { + None + }, + ) + } fn module_to_interface( &self, module: &str, @@ -1304,3 +1803,59 @@ fn validate_func_sig(name: &str, expected: &FuncType, ty: &wasmparser::FuncType) Ok(()) } + +fn match_payload_prefix(name: &str, prefix: &str) -> Option<(String, usize)> { + let suffix = name.strip_prefix(prefix)?; + let index = suffix.find(']')?; + Some(( + suffix[index + 1..].to_owned(), + suffix[..index].parse().ok()?, + )) +} + +/// Retrieve the specified function from the specified world or interface, along +/// with the future or stream type at the specified index. +/// +/// The index refers to the entry in the list returned by +/// `Function::find_futures_and_streams`. +fn get_future_or_stream_type( + resolve: &Resolve, + world: &World, + (name, index): &(String, usize), + interface: Option, + imported: bool, +) -> Result<(Function, TypeId)> { + let function = get_function(resolve, world, name, interface, imported)?; + let ty = function.find_futures_and_streams(resolve)[*index]; + Ok((function, ty)) +} + +fn get_function( + resolve: &Resolve, + world: &World, + name: &str, + interface: Option, + imported: bool, +) -> Result { + let function = if let Some(id) = interface { + resolve.interfaces[id] + .functions + .get(name) + .cloned() + .map(WorldItem::Function) + } else if imported { + world + .imports + .get(&WorldKey::Name(name.to_string())) + .cloned() + } else { + world + .exports + .get(&WorldKey::Name(name.to_string())) + .cloned() + }; + let Some(WorldItem::Function(function)) = function else { + bail!("no export `{name}` export found"); + }; + Ok(function) +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit index 059722ab86..936851fb5b 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit @@ -24,7 +24,7 @@ package wasi:filesystem@0.2.0-rc-2023-11-10; /// /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { - use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream, error}; + use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream, %error}; use wasi:clocks/wall-clock@0.2.0-rc-2023-11-10.{datetime}; /// File size or length of a region within a file. @@ -630,5 +630,5 @@ interface types { /// /// Note that this function is fallible because not all stream-related /// errors are filesystem-related errors. - filesystem-error-code: func(err: borrow) -> option; + filesystem-error-code: func(err: borrow<%error>) -> option; } diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit index 31918acbb4..b0e8b1ed9a 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit @@ -1,7 +1,7 @@ package wasi:io@0.2.0-rc-2023-11-10; -interface error { +interface %error { /// A resource which represents some error information. /// /// The only method provided by this resource is `to-debug-string`, @@ -21,7 +21,7 @@ interface error { /// /// The set of functions which can "downcast" an `error` into a more /// concrete type is open. - resource error { + resource %error { /// Returns a string that is suitable to assist humans in debugging /// this error. /// diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit index f6f7fe0e87..7ccaf0dbac 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit @@ -6,7 +6,7 @@ package wasi:io@0.2.0-rc-2023-11-10; /// In the future, the component model is expected to add built-in stream types; /// when it does, they are expected to subsume this API. interface streams { - use error.{error}; + use %error.{%error}; use poll.{pollable}; /// An error for input-stream and output-stream operations. @@ -14,7 +14,7 @@ interface streams { /// The last operation (a write or flush) failed before completion. /// /// More information is available in the `error` payload. - last-operation-failed(error), + last-operation-failed(%error), /// The stream is closed: no more input will be accepted by the /// stream. A closed output-stream will return this error on all /// future operations. diff --git a/crates/wit-component/tests/interfaces/wasi-http/http.wit.print b/crates/wit-component/tests/interfaces/wasi-http/http.wit.print index ff5dd9b189..0534d5f1bb 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/http.wit.print +++ b/crates/wit-component/tests/interfaces/wasi-http/http.wit.print @@ -6,7 +6,7 @@ package wasi:http@0.2.0-rc-2023-12-05; interface types { use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10.{duration}; use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream}; - use wasi:io/error@0.2.0-rc-2023-11-10.{error as io-error}; + use wasi:io/%error@0.2.0-rc-2023-11-10.{%error as io-error}; use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; /// This type corresponds to HTTP standard Methods. @@ -556,7 +556,7 @@ interface outgoing-handler { /// outgoing HTTP requests. world proxy { import wasi:random/random@0.2.0-rc-2023-11-10; - import wasi:io/error@0.2.0-rc-2023-11-10; + import wasi:io/%error@0.2.0-rc-2023-11-10; import wasi:io/poll@0.2.0-rc-2023-11-10; import wasi:io/streams@0.2.0-rc-2023-11-10; import wasi:cli/stdout@0.2.0-rc-2023-12-05; diff --git a/crates/wit-component/tests/interfaces/wasi-http/types.wit b/crates/wit-component/tests/interfaces/wasi-http/types.wit index 0f698e769e..eb613d4fac 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/types.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/types.wit @@ -4,7 +4,7 @@ interface types { use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10.{duration}; use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream}; - use wasi:io/error@0.2.0-rc-2023-11-10.{error as io-error}; + use wasi:io/%error@0.2.0-rc-2023-11-10.{%error as io-error}; use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; /// This type corresponds to HTTP standard Methods. diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs index 98fded8559..6e68f966cd 100644 --- a/crates/wit-encoder/src/from_parser.rs +++ b/crates/wit-encoder/src/from_parser.rs @@ -211,12 +211,13 @@ impl<'a> Converter<'a> { let output = self.convert_enum(enum_); TypeDefKind::Enum(output) } - wit_parser::TypeDefKind::Future(_) => { - todo!("Enable once wit-encoder supports `future`") + wit_parser::TypeDefKind::Future(ty) => { + TypeDefKind::Type(Type::future(self.convert_option_type(ty))) } - wit_parser::TypeDefKind::Stream(_) => { - todo!("Enable once wit-encoder supports `stream`") + wit_parser::TypeDefKind::Stream(ty) => { + TypeDefKind::Type(Type::stream(self.convert_type(ty))) } + wit_parser::TypeDefKind::Error => TypeDefKind::Type(Type::Error), // all the following are just `type` declarations wit_parser::TypeDefKind::Option(ty) => { let output = Type::option(self.convert_type(ty)); @@ -250,6 +251,10 @@ impl<'a> Converter<'a> { } } + fn convert_option_type(&self, ty: &Option) -> Option { + ty.as_ref().map(|ty| self.convert_type(ty)) + } + fn convert_type(&self, type_: &wit_parser::Type) -> Type { match type_ { wit_parser::Type::Bool => Type::Bool, @@ -283,12 +288,13 @@ impl<'a> Converter<'a> { Type::list(self.convert_type(type_)) } wit_parser::TypeDefKind::Handle(handle) => self.handle_to_type(handle), - wit_parser::TypeDefKind::Future(_) => { - todo!("Enable once wit-encoder supports `future`") + wit_parser::TypeDefKind::Future(type_) => { + Type::future(self.convert_option_type(type_)) } - wit_parser::TypeDefKind::Stream(_) => { - todo!("Enable once wit-encoder supports `stream`") + wit_parser::TypeDefKind::Stream(type_) => { + Type::stream(self.convert_type(type_)) } + wit_parser::TypeDefKind::Error => Type::Error, wit_parser::TypeDefKind::Record(_) | wit_parser::TypeDefKind::Resource | wit_parser::TypeDefKind::Flags(_) diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index 8370fe9ab4..700f089ffe 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -27,6 +27,9 @@ pub enum Type { Result(Box), List(Box), Tuple(Tuple), + Future(Option>), + Stream(Box), + Error, Named(Ident), } @@ -60,6 +63,12 @@ impl Type { types: types.into_iter().collect(), }) } + pub fn future(type_: Option) -> Self { + Type::Future(type_.map(Box::new)) + } + pub fn stream(type_: Type) -> Self { + Type::Stream(Box::new(type_)) + } pub fn named(name: impl Into) -> Self { Type::Named(name.into()) } @@ -108,6 +117,16 @@ impl Display for Type { write!(f, "list<{type_}>") } Type::Tuple(tuple) => tuple.fmt(f), + Type::Future(None) => { + write!(f, "future") + } + Type::Future(Some(type_)) => { + write!(f, "future<{type_}>") + } + Type::Stream(type_) => { + write!(f, "stream<{type_}>") + } + Type::Error => write!(f, "error"), } } } diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index f3d7fc82ed..4ce7808d17 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -121,12 +121,14 @@ impl From for WasmType { /// variants of the ABI, one specialized for the "guest" importing and calling /// a function defined and exported in the "host", and the other specialized for /// the "host" importing and calling a function defined and exported in the "guest". -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum AbiVariant { /// The guest is importing and calling the function. GuestImport, /// The guest is defining and exporting the function. GuestExport, + GuestImportAsync, + GuestExportAsync, } impl Resolve { @@ -135,6 +137,15 @@ impl Resolve { /// The first entry returned is the list of parameters and the second entry /// is the list of results for the wasm function signature. pub fn wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature { + if let AbiVariant::GuestImportAsync = variant { + return WasmSignature { + params: vec![WasmType::Pointer; 2], + indirect_params: true, + results: vec![WasmType::I32], + retptr: true, + }; + } + const MAX_FLAT_PARAMS: usize = 16; const MAX_FLAT_RESULTS: usize = 1; @@ -151,7 +162,10 @@ impl Resolve { } else { if matches!( (&func.kind, variant), - (crate::FunctionKind::Method(_), AbiVariant::GuestExport) + ( + crate::FunctionKind::Method(_), + AbiVariant::GuestExport | AbiVariant::GuestExportAsync + ) ) { // Guest exported methods always receive resource rep as first argument // @@ -164,6 +178,15 @@ impl Resolve { } } + if let AbiVariant::GuestExportAsync = variant { + return WasmSignature { + params, + indirect_params, + results: vec![WasmType::Pointer], + retptr: false, + }; + } + let mut results = Vec::new(); for ty in func.results.iter_types() { self.push_flat(ty, &mut results) @@ -185,6 +208,7 @@ impl Resolve { AbiVariant::GuestExport => { results.push(WasmType::Pointer); } + _ => unreachable!(), } } @@ -274,6 +298,10 @@ impl Resolve { result.push(WasmType::I32); } + TypeDefKind::Error => { + result.push(WasmType::I32); + } + TypeDefKind::Unknown => unreachable!(), }, } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 881d2b2c02..2d11e2e91e 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -741,6 +741,7 @@ enum Type<'a> { Result(Result_<'a>), Future(Future<'a>), Stream(Stream<'a>), + Error(Span), } enum Handle<'a> { @@ -897,8 +898,7 @@ struct Result_<'a> { struct Stream<'a> { span: Span, - element: Option>>, - end: Option>>, + ty: Box>, } struct NamedFunc<'a> { @@ -1403,29 +1403,21 @@ impl<'a> Type<'a> { Ok(Type::Future(Future { span, ty })) } - // stream - // stream<_, Z> // stream // stream Some((span, Token::Stream)) => { - let mut element = None; - let mut end = None; - - if tokens.eat(Token::LessThan)? { - if tokens.eat(Token::Underscore)? { - tokens.expect(Token::Comma)?; - end = Some(Box::new(Type::parse(tokens)?)); - } else { - element = Some(Box::new(Type::parse(tokens)?)); - if tokens.eat(Token::Comma)? { - end = Some(Box::new(Type::parse(tokens)?)); - } - }; - tokens.expect(Token::GreaterThan)?; - }; - Ok(Type::Stream(Stream { span, element, end })) + tokens.expect(Token::LessThan)?; + let ty = Type::parse(tokens)?; + tokens.expect(Token::GreaterThan)?; + Ok(Type::Stream(Stream { + span, + ty: Box::new(ty), + })) } + // error + Some((span, Token::Error)) => Ok(Type::Error(span)), + // own Some((_span, Token::Own)) => { tokens.expect(Token::LessThan)?; @@ -1471,7 +1463,8 @@ impl<'a> Type<'a> { | Type::F32(span) | Type::F64(span) | Type::Char(span) - | Type::String(span) => *span, + | Type::String(span) + | Type::Error(span) => *span, Type::Name(id) => id.span, Type::List(l) => l.span, Type::Handle(h) => h.span(), diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index a3a59cb945..3a6218a745 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -78,6 +78,7 @@ pub enum Token { Result_, Future, Stream, + Error, List, Underscore, As, @@ -297,6 +298,7 @@ impl<'a> Tokenizer<'a> { "result" => Result_, "future" => Future, "stream" => Stream, + "error" => Error, "list" => List, "_" => Underscore, "as" => As, @@ -550,6 +552,7 @@ impl Token { Result_ => "keyword `result`", Future => "keyword `future`", Stream => "keyword `stream`", + Error => "keyword `error`", List => "keyword `list`", Underscore => "keyword `_`", Id => "an identifier", diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index f32b502331..ebf7cf54e0 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -95,7 +95,8 @@ enum Key { Option(Type), Result(Option, Option), Future(Option), - Stream(Option, Option), + Stream(Type), + Error, } enum TypeItem<'a, 'b> { @@ -1254,10 +1255,8 @@ impl<'a> Resolver<'a> { ast::Type::Future(t) => { TypeDefKind::Future(self.resolve_optional_type(t.ty.as_deref(), stability)?) } - ast::Type::Stream(s) => TypeDefKind::Stream(Stream { - element: self.resolve_optional_type(s.element.as_deref(), stability)?, - end: self.resolve_optional_type(s.end.as_deref(), stability)?, - }), + ast::Type::Stream(s) => TypeDefKind::Stream(self.resolve_type(&s.ty, stability)?), + ast::Type::Error(_) => TypeDefKind::Error, }) } @@ -1359,7 +1358,8 @@ impl<'a> Resolver<'a> { TypeDefKind::Option(t) => Key::Option(*t), TypeDefKind::Result(r) => Key::Result(r.ok, r.err), TypeDefKind::Future(ty) => Key::Future(*ty), - TypeDefKind::Stream(s) => Key::Stream(s.element, s.end), + TypeDefKind::Stream(ty) => Key::Stream(*ty), + TypeDefKind::Error => Key::Error, TypeDefKind::Unknown => unreachable!(), }; let id = self.anon_types.entry(key).or_insert_with(|| { @@ -1544,9 +1544,9 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { | ast::Type::Char(_) | ast::Type::String(_) | ast::Type::Flags(_) - | ast::Type::Enum(_) => {} + | ast::Type::Enum(_) + | ast::Type::Error(_) => {} ast::Type::Name(name) => deps.push(name.clone()), - ast::Type::List(list) => collect_deps(&list.ty, deps), ast::Type::Handle(handle) => match handle { ast::Handle::Own { resource } => deps.push(resource.clone()), ast::Handle::Borrow { resource } => deps.push(resource.clone()), @@ -1569,7 +1569,9 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { } } } - ast::Type::Option(ty) => collect_deps(&ty.ty, deps), + ast::Type::Option(ast::Option_ { ty, .. }) + | ast::Type::List(ast::List { ty, .. }) + | ast::Type::Stream(ast::Stream { ty, .. }) => collect_deps(ty, deps), ast::Type::Result(r) => { if let Some(ty) = &r.ok { collect_deps(ty, deps); @@ -1583,13 +1585,5 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { collect_deps(t, deps) } } - ast::Type::Stream(s) => { - if let Some(t) = &s.element { - collect_deps(t, deps); - } - if let Some(t) = &s.end { - collect_deps(t, deps); - } - } } } diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index dbfdcb2e4c..b69f07c855 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -1260,15 +1260,16 @@ impl WitPackageDecoder<'_> { | TypeDefKind::Tuple(_) | TypeDefKind::Option(_) | TypeDefKind::Result(_) - | TypeDefKind::Handle(_) => {} + | TypeDefKind::Handle(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::Error => {} TypeDefKind::Resource | TypeDefKind::Record(_) | TypeDefKind::Enum(_) | TypeDefKind::Variant(_) - | TypeDefKind::Flags(_) - | TypeDefKind::Future(_) - | TypeDefKind::Stream(_) => { + | TypeDefKind::Flags(_) => { bail!("unexpected unnamed type of kind '{}'", kind.as_str()); } TypeDefKind::Unknown => unreachable!(), @@ -1393,6 +1394,14 @@ impl WitPackageDecoder<'_> { let id = self.type_map[&(*id).into()]; Ok(TypeDefKind::Handle(Handle::Borrow(id))) } + + ComponentDefinedType::Future(ty) => Ok(TypeDefKind::Future( + ty.as_ref().map(|ty| self.convert_valtype(ty)).transpose()?, + )), + + ComponentDefinedType::Stream(ty) => Ok(TypeDefKind::Stream(self.convert_valtype(ty)?)), + + ComponentDefinedType::Error => Ok(TypeDefKind::Error), } } @@ -1663,11 +1672,34 @@ impl Registrar<'_> { Ok(()) } + ComponentDefinedType::Future(payload) => { + let ty = match &self.resolve.types[id].kind { + TypeDefKind::Future(p) => p, + TypeDefKind::Type(Type::Id(_)) => return Ok(()), + _ => bail!("expected a future"), + }; + match (payload, ty) { + (Some(a), Some(b)) => self.valtype(a, b), + (None, None) => Ok(()), + _ => bail!("disagreement on future payload"), + } + } + + ComponentDefinedType::Stream(payload) => { + let ty = match &self.resolve.types[id].kind { + TypeDefKind::Stream(p) => p, + TypeDefKind::Type(Type::Id(_)) => return Ok(()), + _ => bail!("expected a stream"), + }; + self.valtype(payload, ty) + } + // These have no recursive structure so they can bail out. ComponentDefinedType::Flags(_) | ComponentDefinedType::Enum(_) | ComponentDefinedType::Own(_) - | ComponentDefinedType::Borrow(_) => Ok(()), + | ComponentDefinedType::Borrow(_) + | ComponentDefinedType::Error => Ok(()), } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 9a0cc6f4be..a2cf8bffa8 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -569,7 +569,8 @@ pub enum TypeDefKind { Result(Result_), List(Type), Future(Option), - Stream(Stream), + Stream(Type), + Error, Type(Type), /// This represents a type of unknown structure imported from a foreign @@ -598,6 +599,7 @@ impl TypeDefKind { TypeDefKind::List(_) => "list", TypeDefKind::Future(_) => "future", TypeDefKind::Stream(_) => "stream", + TypeDefKind::Error => "error", TypeDefKind::Type(_) => "type", TypeDefKind::Unknown => "unknown", } @@ -780,13 +782,6 @@ pub struct Result_ { pub err: Option, } -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize))] -pub struct Stream { - pub element: Option, - pub end: Option, -} - #[derive(Clone, Default, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct Docs { @@ -991,6 +986,82 @@ impl Function { }, } } + /// Collect any future and stream types appearing in the signature of this + /// function by doing a depth-first search over the parameter types and then + /// the result types. + /// + /// For example, given the WIT function `foo: func(x: future>, + /// y: u32) -> stream`, we would return `[future, + /// future>, stream]`. + /// + /// This may be used by binding generators to refer to specific `future` and + /// `stream` types when importing canonical built-ins such as `stream.new`, + /// `future.read`, etc. Using the example above, the import + /// `[future-new-0]foo` would indicate a call to `future.new` for the type + /// `future`. Likewise, `[future-new-1]foo` would indicate a call to + /// `future.new` for `future>`, and `[stream-new-2]foo` would + /// indicate a call to `stream.new` for `stream`. + pub fn find_futures_and_streams(&self, resolve: &Resolve) -> Vec { + let mut results = Vec::new(); + for (_, ty) in self.params.iter() { + find_futures_and_streams(resolve, *ty, &mut results); + } + for ty in self.results.iter_types() { + find_futures_and_streams(resolve, *ty, &mut results); + } + results + } +} + +fn find_futures_and_streams(resolve: &Resolve, ty: Type, results: &mut Vec) { + if let Type::Id(id) = ty { + match &resolve.types[id].kind { + TypeDefKind::Resource + | TypeDefKind::Handle(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Error => {} + TypeDefKind::Record(r) => { + for Field { ty, .. } in &r.fields { + find_futures_and_streams(resolve, *ty, results); + } + } + TypeDefKind::Tuple(t) => { + for ty in &t.types { + find_futures_and_streams(resolve, *ty, results); + } + } + TypeDefKind::Variant(v) => { + for Case { ty, .. } in &v.cases { + if let Some(ty) = ty { + find_futures_and_streams(resolve, *ty, results); + } + } + } + TypeDefKind::Option(ty) | TypeDefKind::List(ty) | TypeDefKind::Type(ty) => { + find_futures_and_streams(resolve, *ty, results); + } + TypeDefKind::Result(r) => { + if let Some(ty) = r.ok { + find_futures_and_streams(resolve, ty, results); + } + if let Some(ty) = r.err { + find_futures_and_streams(resolve, ty, results); + } + } + TypeDefKind::Future(ty) => { + if let Some(ty) = ty { + find_futures_and_streams(resolve, *ty, results); + } + results.push(id); + } + TypeDefKind::Stream(ty) => { + find_futures_and_streams(resolve, *ty, results); + results.push(id); + } + TypeDefKind::Unknown => unreachable!(), + } + } } /// Representation of the stability attributes associated with a world, diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index e8dfac1994..6c68106b86 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -129,7 +129,8 @@ pub trait TypeIdVisitor { TypeDefKind::Type(t) | TypeDefKind::List(t) | TypeDefKind::Option(t) - | TypeDefKind::Future(Some(t)) => self.visit_type(resolve, t), + | TypeDefKind::Future(Some(t)) + | TypeDefKind::Stream(t) => self.visit_type(resolve, t), TypeDefKind::Handle(handle) => match handle { crate::Handle::Own(ty) => self.visit_type_id(resolve, *ty), crate::Handle::Borrow(ty) => self.visit_type_id(resolve, *ty), @@ -160,15 +161,10 @@ pub trait TypeIdVisitor { self.visit_type(resolve, ty); } } - TypeDefKind::Stream(s) => { - if let Some(ty) = &s.element { - self.visit_type(resolve, ty); - } - if let Some(ty) = &s.end { - self.visit_type(resolve, ty); - } - } - TypeDefKind::Flags(_) | TypeDefKind::Enum(_) | TypeDefKind::Future(None) => {} + TypeDefKind::Error + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Future(None) => {} TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 665361c7a5..ba7a59ead5 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -572,7 +572,8 @@ package {name} is defined in two different locations:\n\ | TypeDefKind::Option(_) | TypeDefKind::Result(_) | TypeDefKind::Future(_) - | TypeDefKind::Stream(_) => false, + | TypeDefKind::Stream(_) + | TypeDefKind::Error => false, TypeDefKind::Type(t) => self.all_bits_valid(t), TypeDefKind::Handle(h) => match h { @@ -2985,7 +2986,7 @@ impl Remap { } } } - Option(t) => self.update_ty(resolve, t, span)?, + Option(t) | List(t) | Stream(t) => self.update_ty(resolve, t, span)?, Result(r) => { if let Some(ty) = &mut r.ok { self.update_ty(resolve, ty, span)?; @@ -2994,16 +2995,8 @@ impl Remap { self.update_ty(resolve, ty, span)?; } } - List(t) => self.update_ty(resolve, t, span)?, Future(Some(t)) => self.update_ty(resolve, t, span)?, - Stream(t) => { - if let Some(ty) = &mut t.element { - self.update_ty(resolve, ty, span)?; - } - if let Some(ty) = &mut t.end { - self.update_ty(resolve, ty, span)?; - } - } + Error => {} // Note that `update_ty` is specifically not used here as typedefs // because for the `type a = b` form that doesn't force `a` to be a @@ -3394,18 +3387,15 @@ impl Remap { TypeDefKind::Flags(_) => false, TypeDefKind::Tuple(t) => t.types.iter().any(|t| self.type_has_borrow(resolve, t)), TypeDefKind::Enum(_) => false, - TypeDefKind::List(ty) | TypeDefKind::Future(Some(ty)) | TypeDefKind::Option(ty) => { - self.type_has_borrow(resolve, ty) - } + TypeDefKind::List(ty) + | TypeDefKind::Future(Some(ty)) + | TypeDefKind::Stream(ty) + | TypeDefKind::Option(ty) => self.type_has_borrow(resolve, ty), TypeDefKind::Result(r) => [&r.ok, &r.err] .iter() .filter_map(|t| t.as_ref()) .any(|t| self.type_has_borrow(resolve, t)), - TypeDefKind::Stream(r) => [&r.element, &r.end] - .iter() - .filter_map(|t| t.as_ref()) - .any(|t| self.type_has_borrow(resolve, t)), - TypeDefKind::Future(None) => false, + TypeDefKind::Future(None) | TypeDefKind::Error => false, TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index 21885df51e..6deec56ac8 100644 --- a/crates/wit-parser/src/resolve/clone.rs +++ b/crates/wit-parser/src/resolve/clone.rs @@ -105,11 +105,12 @@ impl<'a> Cloner<'a> { TypeDefKind::Type(_) | TypeDefKind::Resource | TypeDefKind::Flags(_) - | TypeDefKind::Enum(_) => {} + | TypeDefKind::Enum(_) + | TypeDefKind::Error => {} TypeDefKind::Handle(Handle::Own(ty) | Handle::Borrow(ty)) => { self.type_id(ty); } - TypeDefKind::Option(ty) | TypeDefKind::List(ty) => { + TypeDefKind::Option(ty) | TypeDefKind::List(ty) | TypeDefKind::Stream(ty) => { self.ty(ty); } TypeDefKind::Tuple(list) => { @@ -142,7 +143,6 @@ impl<'a> Cloner<'a> { self.ty(ty); } } - TypeDefKind::Stream(_) => unimplemented!(), TypeDefKind::Unknown => {} } } diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index 294ad8d6a0..8631db5cae 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -255,9 +255,11 @@ impl SizeAlign { // A resource is represented as an index. // A future is represented as an index. // A stream is represented as an index. - TypeDefKind::Handle(_) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) => { - int_size_align(Int::U32) - } + // An error is represented as an index. + TypeDefKind::Handle(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::Error => int_size_align(Int::U32), // This shouldn't be used for anything since raw resources aren't part of the ABI -- just handles to // them. TypeDefKind::Resource => ElementInfo::new( diff --git a/crates/wit-parser/tests/ui/comments.wit.json b/crates/wit-parser/tests/ui/comments.wit.json index f2a04f9d39..7c7a44e1c4 100644 --- a/crates/wit-parser/tests/ui/comments.wit.json +++ b/crates/wit-parser/tests/ui/comments.wit.json @@ -27,10 +27,7 @@ { "name": "bar", "kind": { - "stream": { - "element": 0, - "end": null - } + "stream": 0 }, "owner": { "interface": 0 diff --git a/crates/wit-parser/tests/ui/types.wit b/crates/wit-parser/tests/ui/types.wit index 4d007b64fe..d67939799d 100644 --- a/crates/wit-parser/tests/ui/types.wit +++ b/crates/wit-parser/tests/ui/types.wit @@ -47,12 +47,9 @@ interface types { type t45 = list>>; type t46 = t44; type t47 = %t44; - type t48 = stream; - type t49 = stream<_, u32>; - type t50 = stream; - type t51 = stream; - type t52 = future; - type t53 = future; + type t48 = stream; + type t49 = future; + type t50 = future; /// type order doesn't matter type foo = bar; diff --git a/crates/wit-parser/tests/ui/types.wit.json b/crates/wit-parser/tests/ui/types.wit.json index 8874e3541d..1b5e70e51d 100644 --- a/crates/wit-parser/tests/ui/types.wit.json +++ b/crates/wit-parser/tests/ui/types.wit.json @@ -53,11 +53,8 @@ "t48": 49, "t49": 50, "t50": 51, - "t51": 52, - "t52": 53, - "t53": 54, - "bar": 55, - "foo": 56 + "bar": 52, + "foo": 53 }, "functions": {}, "package": 0 @@ -677,10 +674,7 @@ { "name": "t48", "kind": { - "stream": { - "element": "u32", - "end": "u32" - } + "stream": "u32" }, "owner": { "interface": 0 @@ -688,42 +682,6 @@ }, { "name": "t49", - "kind": { - "stream": { - "element": null, - "end": "u32" - } - }, - "owner": { - "interface": 0 - } - }, - { - "name": "t50", - "kind": { - "stream": { - "element": "u32", - "end": null - } - }, - "owner": { - "interface": 0 - } - }, - { - "name": "t51", - "kind": { - "stream": { - "element": null, - "end": null - } - }, - "owner": { - "interface": 0 - } - }, - { - "name": "t52", "kind": { "future": "u32" }, @@ -732,7 +690,7 @@ } }, { - "name": "t53", + "name": "t50", "kind": { "future": null }, @@ -752,7 +710,7 @@ { "name": "foo", "kind": { - "type": 55 + "type": 52 }, "owner": { "interface": 0 diff --git a/src/bin/wasm-tools/dump.rs b/src/bin/wasm-tools/dump.rs index 8a8ef69219..94f22f4127 100644 --- a/src/bin/wasm-tools/dump.rs +++ b/src/bin/wasm-tools/dump.rs @@ -422,6 +422,29 @@ impl<'a> Dump<'a> { | CanonicalFunction::ThreadHwConcurrency => { ("core func", &mut i.core_funcs) } + CanonicalFunction::TaskBackpressure + | CanonicalFunction::TaskReturn { .. } + | CanonicalFunction::TaskWait { .. } + | CanonicalFunction::TaskPoll { .. } + | CanonicalFunction::TaskYield { .. } + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::StreamNew { .. } + | CanonicalFunction::StreamRead { .. } + | CanonicalFunction::StreamWrite { .. } + | CanonicalFunction::StreamCancelRead { .. } + | CanonicalFunction::StreamCancelWrite { .. } + | CanonicalFunction::StreamCloseReadable { .. } + | CanonicalFunction::StreamCloseWritable { .. } + | CanonicalFunction::FutureNew { .. } + | CanonicalFunction::FutureRead { .. } + | CanonicalFunction::FutureWrite { .. } + | CanonicalFunction::FutureCancelRead { .. } + | CanonicalFunction::FutureCancelWrite { .. } + | CanonicalFunction::FutureCloseReadable { .. } + | CanonicalFunction::FutureCloseWritable { .. } + | CanonicalFunction::ErrorNew { .. } + | CanonicalFunction::ErrorDebugMessage { .. } + | CanonicalFunction::ErrorDrop => ("core func", &mut i.core_funcs), }; write!(me.state, "[{} {}] {:?}", name, inc(col), f)?; diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr index d0d274b716..c337c23bce 100644 --- a/tests/cli/validate-unknown-features.wat.stderr +++ b/tests/cli/validate-unknown-features.wat.stderr @@ -1,4 +1,4 @@ error: invalid value 'unknown' for '--features ': unknown feature `unknown` -Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, mvp, wasm1, wasm2, wasm3, all +Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, component-model-async, mvp, wasm1, wasm2, wasm3, all For more information, try '--help'. From c6aef4719840276ce55413aea17df39d79fb993e Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 5 Nov 2024 16:27:39 -0700 Subject: [PATCH 2/5] add wasmparser::WasmFeatures support to wasm-compose Signed-off-by: Joel Dice --- crates/wasm-compose/src/composer.rs | 66 +++++++++++------ crates/wasm-compose/src/graph.rs | 102 +++++++++++++++++++++------ crates/wasm-compose/tests/compose.rs | 2 +- src/bin/wasm-tools/compose.rs | 5 +- 4 files changed, 131 insertions(+), 44 deletions(-) diff --git a/crates/wasm-compose/src/composer.rs b/crates/wasm-compose/src/composer.rs index 84ce8339df..3db217814e 100644 --- a/crates/wasm-compose/src/composer.rs +++ b/crates/wasm-compose/src/composer.rs @@ -14,7 +14,7 @@ use std::{collections::VecDeque, ffi::OsStr, path::Path}; use wasmparser::{ component_types::{ComponentEntityType, ComponentInstanceTypeId}, types::TypesRef, - ComponentExternalKind, ComponentTypeRef, + ComponentExternalKind, ComponentTypeRef, WasmFeatures, }; /// The root component name used in configuration. @@ -72,9 +72,13 @@ struct CompositionGraphBuilder<'a> { } impl<'a> CompositionGraphBuilder<'a> { - fn new(root_path: &Path, config: &'a Config) -> Result { + fn new(root_path: &Path, config: &'a Config, features: WasmFeatures) -> Result { let mut graph = CompositionGraph::new(); - graph.add_component(Component::from_file(ROOT_COMPONENT_NAME, root_path)?)?; + graph.add_component(Component::from_file( + ROOT_COMPONENT_NAME, + root_path, + features, + )?)?; let definitions = config .definitions @@ -87,7 +91,7 @@ impl<'a> CompositionGraphBuilder<'a> { ) })?; - let component = Component::from_file(name, config.dir.join(path))?; + let component = Component::from_file(name, config.dir.join(path), features)?; Ok((graph.add_component(component)?, None)) }) @@ -105,19 +109,19 @@ impl<'a> CompositionGraphBuilder<'a> { /// /// If a component with the given name already exists, its id is returned. /// Returns `Ok(None)` if a matching component cannot be found. - fn add_component(&mut self, name: &str) -> Result> { + fn add_component(&mut self, name: &str, features: WasmFeatures) -> Result> { if let Some((id, _)) = self.graph.get_component_by_name(name) { return Ok(Some(id)); } - match self.find_component(name)? { + match self.find_component(name, features)? { Some(component) => Ok(Some(self.graph.add_component(component)?)), None => Ok(None), } } /// Finds the component with the given name on disk. - fn find_component(&self, name: &str) -> Result>> { + fn find_component(&self, name: &str, features: WasmFeatures) -> Result>> { // Check the config for an explicit path (must be a valid component) if let Some(dep) = self.config.dependencies.get(name) { log::debug!( @@ -127,13 +131,14 @@ impl<'a> CompositionGraphBuilder<'a> { return Ok(Some(Component::from_file( name, self.config.dir.join(&dep.path), + features, )?)); } // Otherwise, search the paths for a valid component with the same name log::info!("searching for a component with name `{name}`"); for dir in std::iter::once(&self.config.dir).chain(self.config.search_paths.iter()) { - if let Some(component) = Self::parse_component(dir, name)? { + if let Some(component) = Self::parse_component(dir, name, features)? { return Ok(Some(component)); } } @@ -144,7 +149,11 @@ impl<'a> CompositionGraphBuilder<'a> { /// Parses a component from the given directory, if it exists. /// /// Returns `Ok(None)` if the component does not exist. - fn parse_component(dir: &Path, name: &str) -> Result>> { + fn parse_component( + dir: &Path, + name: &str, + features: WasmFeatures, + ) -> Result>> { let mut path = dir.join(name); for ext in ["wasm", "wat"] { @@ -154,7 +163,7 @@ impl<'a> CompositionGraphBuilder<'a> { continue; } - return Ok(Some(Component::from_file(name, &path)?)); + return Ok(Some(Component::from_file(name, &path, features)?)); } Ok(None) @@ -165,12 +174,17 @@ impl<'a> CompositionGraphBuilder<'a> { /// Returns an index into `instances` for the instance being instantiated. /// /// Returns `Ok(None)` if a component to instantiate cannot be found. - fn instantiate(&mut self, name: &str, component_name: &str) -> Result> { + fn instantiate( + &mut self, + name: &str, + component_name: &str, + features: WasmFeatures, + ) -> Result> { if let Some(index) = self.instances.get_index_of(name) { return Ok(Some((index, true))); } - match self.add_component(component_name)? { + match self.add_component(component_name, features)? { Some(component_id) => { let (index, prev) = self .instances @@ -301,13 +315,18 @@ impl<'a> CompositionGraphBuilder<'a> { /// Processes a dependency in the graph. /// /// Returns `Ok(Some(index))` if the dependency resulted in a new dependency instance being created. - fn process_dependency(&mut self, dependency: Dependency) -> Result> { + fn process_dependency( + &mut self, + dependency: Dependency, + features: WasmFeatures, + ) -> Result> { match dependency.kind { DependencyKind::Instance { instance, export } => self.process_instance_dependency( dependency.dependent, dependency.import, &instance, export.as_deref(), + features, ), DependencyKind::Definition { index, export } => { // The dependency is on a definition component, so we simply connect the dependent to the definition's export @@ -348,6 +367,7 @@ impl<'a> CompositionGraphBuilder<'a> { import: InstanceImportRef, instance: &str, export: Option<&str>, + features: WasmFeatures, ) -> Result> { let name = self.config.dependency_name(instance); @@ -356,7 +376,7 @@ impl<'a> CompositionGraphBuilder<'a> { dependent_name = self.instances.get_index(dependent_index).unwrap().0, ); - match self.instantiate(instance, name)? { + match self.instantiate(instance, name, features)? { Some((instance, existing)) => { let (dependent, import_name, import_type) = self.resolve_import_ref(import); @@ -478,12 +498,12 @@ impl<'a> CompositionGraphBuilder<'a> { } /// Build the instantiation graph. - fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> { + fn build(mut self, features: WasmFeatures) -> Result<(InstanceId, CompositionGraph<'a>)> { let mut queue: VecDeque = VecDeque::new(); // Instantiate the root and push its dependencies to the queue let (root_instance, existing) = self - .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME)? + .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME, features)? .unwrap(); assert!(!existing); @@ -492,7 +512,7 @@ impl<'a> CompositionGraphBuilder<'a> { // Process all remaining dependencies in the queue while let Some(dependency) = queue.pop_front() { - if let Some(instance) = self.process_dependency(dependency)? { + if let Some(instance) = self.process_dependency(dependency, features)? { self.push_dependencies(instance, &mut queue)?; } } @@ -511,6 +531,7 @@ impl<'a> CompositionGraphBuilder<'a> { pub struct ComponentComposer<'a> { component: &'a Path, config: &'a Config, + features: WasmFeatures, } impl<'a> ComponentComposer<'a> { @@ -519,8 +540,12 @@ impl<'a> ComponentComposer<'a> { /// ## Arguments /// * `component` - The path to the component to compose. /// * `config` - The configuration to use for the composition. - pub fn new(component: &'a Path, config: &'a Config) -> Self { - Self { component, config } + pub fn new(component: &'a Path, config: &'a Config, features: WasmFeatures) -> Self { + Self { + component, + config, + features, + } } /// Composes a WebAssembly component based on the composer's configuration. @@ -529,7 +554,8 @@ impl<'a> ComponentComposer<'a> { /// Returns the bytes of the composed component. pub fn compose(&self) -> Result> { let (root_instance, graph) = - CompositionGraphBuilder::new(self.component, self.config)?.build()?; + CompositionGraphBuilder::new(self.component, self.config, self.features)? + .build(self.features)?; // If only the root component was instantiated, then there are no resolved dependencies if graph.instances.len() == 1 { diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs index 8bb1dcdbb8..10166d6b51 100644 --- a/crates/wasm-compose/src/graph.rs +++ b/crates/wasm-compose/src/graph.rs @@ -50,7 +50,7 @@ pub struct Component<'a> { impl<'a> Component<'a> { /// Constructs a new component from reading the given file. - pub fn from_file(name: &str, path: impl AsRef) -> Result { + pub fn from_file(name: &str, path: impl AsRef, features: WasmFeatures) -> Result { let path = path.as_ref(); log::info!("parsing WebAssembly component file `{}`", path.display()); let component = Self::parse( @@ -61,6 +61,7 @@ impl<'a> Component<'a> { format!("failed to parse component `{path}`", path = path.display()) })? .into(), + features, ) .with_context(|| format!("failed to parse component `{path}`", path = path.display()))?; @@ -73,7 +74,11 @@ impl<'a> Component<'a> { } /// Constructs a new component from the given bytes. - pub fn from_bytes(name: impl Into, bytes: impl Into>) -> Result { + pub fn from_bytes( + name: impl Into, + bytes: impl Into>, + features: WasmFeatures, + ) -> Result { let mut bytes = bytes.into(); match wat::parse_bytes(bytes.as_ref()).context("failed to parse component")? { @@ -88,6 +93,7 @@ impl<'a> Component<'a> { ComponentName::new(&name.into(), 0)?.to_string(), None, bytes, + features, ) .context("failed to parse component")?; @@ -96,14 +102,15 @@ impl<'a> Component<'a> { Ok(component) } - fn parse(name: String, path: Option, bytes: Cow<'a, [u8]>) -> Result { + fn parse( + name: String, + path: Option, + bytes: Cow<'a, [u8]>, + features: WasmFeatures, + ) -> Result { let mut parser = Parser::new(0); let mut parsers = Vec::new(); - let mut validator = Validator::new_with_features( - WasmFeatures::WASM2 - | WasmFeatures::COMPONENT_MODEL - | WasmFeatures::COMPONENT_MODEL_ASYNC, - ); + let mut validator = Validator::new_with_features(features); let mut imports = IndexMap::new(); let mut exports = IndexMap::new(); @@ -1040,7 +1047,7 @@ mod test { #[test] fn it_rejects_modules() -> Result<()> { - match Component::from_bytes("a", b"(module)".as_ref()) { + match Component::from_bytes("a", b"(module)".as_ref(), WasmFeatures::default()) { Ok(_) => panic!("expected a failure to parse"), Err(e) => assert_eq!( format!("{e:#}"), @@ -1053,7 +1060,7 @@ mod test { #[test] fn it_rejects_invalid_components() -> Result<()> { - match Component::from_bytes("a", b"(component (export \"x\" (func 0)))".as_ref()) { + match Component::from_bytes("a", b"(component (export \"x\" (func 0)))".as_ref(), WasmFeatures::default()) { Ok(_) => panic!("expected a failure to parse"), Err(e) => assert_eq!(format!("{e:#}"), "failed to parse component: unknown function 0: function index out of bounds (at offset 0xb)"), } @@ -1064,9 +1071,17 @@ mod test { #[test] fn it_ensures_unique_component_names() -> Result<()> { let mut graph = CompositionGraph::new(); - graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; - match graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?) { + match graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?) { Ok(_) => panic!("expected a failure to add component"), Err(e) => assert_eq!(format!("{e:#}"), "a component with name `a` already exists"), } @@ -1088,7 +1103,11 @@ mod test { #[test] fn it_instantiates_a_component() -> Result<()> { let mut graph = CompositionGraph::new(); - let id = graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + let id = graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; let id = graph.instantiate(id)?; assert_eq!(graph.get_component_of_instance(id).unwrap().1.name(), "a"); Ok(()) @@ -1104,7 +1123,11 @@ mod test { #[test] fn it_gets_a_component() -> Result<()> { let mut graph = CompositionGraph::new(); - let id = graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + let id = graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; assert_eq!(graph.get_component(id).unwrap().name(), "a"); assert_eq!(graph.get_component_by_name("a").unwrap().1.name(), "a"); Ok(()) @@ -1116,10 +1139,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1149,10 +1174,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1174,10 +1201,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1200,10 +1229,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1227,10 +1258,11 @@ mod test { "a", b"(component (import \"i1\" (func)) (import \"i2\" (instance (export \"no\" (func)))))" .as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", - b"(component (import \"i1\" (func)) (import \"i2\" (core module)) (export \"e1\" (func 0)) (export \"e2\" (core module 0)))".as_ref(), + b"(component (import \"i1\" (func)) (import \"i2\" (core module)) (export \"e1\" (func 0)) (export \"e2\" (core module 0)))".as_ref(), WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1307,10 +1339,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1333,8 +1367,16 @@ mod test { #[test] fn it_encodes_an_empty_component() -> Result<()> { let mut graph = CompositionGraph::new(); - graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; - graph.add_component(Component::from_bytes("b", b"(component)".as_ref())?)?; + graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; + graph.add_component(Component::from_bytes( + "b", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; let encoded = graph.encode(EncodeOptions { define_components: false, @@ -1356,8 +1398,16 @@ mod test { fn it_encodes_component_imports() -> Result<()> { let mut graph = CompositionGraph::new(); // Add a component that doesn't get instantiated (shouldn't be imported) - graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; - let b = graph.add_component(Component::from_bytes("b", b"(component)".as_ref())?)?; + graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; graph.instantiate(b)?; let encoded = graph.encode(EncodeOptions { @@ -1386,8 +1436,16 @@ mod test { fn it_encodes_defined_components() -> Result<()> { let mut graph = CompositionGraph::new(); // Add a component that doesn't get instantiated (shouldn't be imported) - graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; - let b = graph.add_component(Component::from_bytes("b", b"(component)".as_ref())?)?; + graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; graph.instantiate(b)?; let encoded = graph.encode(EncodeOptions { @@ -1428,6 +1486,7 @@ mod test { (export \"e5\" (type 1)) )" .as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", @@ -1440,6 +1499,7 @@ mod test { (import \"i5\" (type (eq 0))) )" .as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; diff --git a/crates/wasm-compose/tests/compose.rs b/crates/wasm-compose/tests/compose.rs index e9e28f231c..f5d9930f9d 100644 --- a/crates/wasm-compose/tests/compose.rs +++ b/crates/wasm-compose/tests/compose.rs @@ -47,7 +47,7 @@ fn component_composing() -> Result<()> { ..Default::default() } }; - let composer = ComponentComposer::new(&root_path, &config); + let composer = ComponentComposer::new(&root_path, &config, WasmFeatures::default()); let r = composer.compose(); let (output, baseline_path) = if error_path.is_file() { diff --git a/src/bin/wasm-tools/compose.rs b/src/bin/wasm-tools/compose.rs index 60f8b212b3..3da0633a97 100644 --- a/src/bin/wasm-tools/compose.rs +++ b/src/bin/wasm-tools/compose.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use clap::Parser; use std::path::{Path, PathBuf}; use wasm_compose::{composer::ComponentComposer, config::Config}; -use wasmparser::Validator; +use wasmparser::{Validator, WasmFeatures}; /// WebAssembly component composer. /// @@ -59,7 +59,8 @@ impl Opts { let config = self.create_config()?; log::debug!("configuration:\n{:#?}", config); - let bytes = ComponentComposer::new(&self.component, &config).compose()?; + let bytes = + ComponentComposer::new(&self.component, &config, WasmFeatures::default()).compose()?; self.output.output_wasm(&self.general, &bytes, self.wat)?; From a245168480c423333a68b1ad3e498d3c565f5853 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 5 Nov 2024 16:44:10 -0700 Subject: [PATCH 3/5] fix no-std build in readers.rs Signed-off-by: Joel Dice --- crates/wasmparser/src/readers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasmparser/src/readers.rs b/crates/wasmparser/src/readers.rs index 4213b33d74..47a54deece 100644 --- a/crates/wasmparser/src/readers.rs +++ b/crates/wasmparser/src/readers.rs @@ -42,8 +42,8 @@ impl<'a> FromReader<'a> for bool { match reader.read_u8()? { 0 => Ok(false), 1 => Ok(true), - v => Err(BinaryReaderError::new( - format!("invalid boolean value: {v}"), + _ => Err(BinaryReaderError::new( + "invalid boolean value", reader.original_position() - 1, )), } From d5a6c1bd1748dd1aaffe3e62fc65c306d2e4d468 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 6 Nov 2024 08:33:46 -0700 Subject: [PATCH 4/5] rename `error` to `error-context` per latest spec Signed-off-by: Joel Dice --- crates/wasm-compose/src/encoding.rs | 8 +++---- crates/wasm-encoder/src/component/types.rs | 4 ++-- crates/wasm-encoder/src/reencode/component.rs | 2 +- .../wasmparser/src/readers/component/types.rs | 6 ++--- crates/wasmparser/src/validator/component.rs | 4 ++-- .../src/validator/component_types.rs | 22 +++++++++---------- crates/wasmprinter/src/component.rs | 2 +- crates/wit-component/src/encoding.rs | 2 +- crates/wit-component/src/encoding/types.rs | 6 ++--- crates/wit-component/src/printing.rs | 4 ++-- .../wasi-http/deps/filesystem/types.wit | 4 ++-- .../interfaces/wasi-http/deps/io/error.wit | 4 ++-- .../interfaces/wasi-http/deps/io/streams.wit | 4 ++-- .../tests/interfaces/wasi-http/types.wit | 2 +- crates/wit-encoder/src/from_parser.rs | 4 ++-- crates/wit-encoder/src/ty.rs | 4 ++-- crates/wit-parser/src/abi.rs | 2 +- crates/wit-parser/src/ast.rs | 6 ++--- crates/wit-parser/src/ast/lex.rs | 6 ++--- crates/wit-parser/src/ast/resolve.rs | 8 +++---- crates/wit-parser/src/decoding.rs | 6 ++--- crates/wit-parser/src/lib.rs | 6 ++--- crates/wit-parser/src/live.rs | 2 +- crates/wit-parser/src/resolve.rs | 6 ++--- crates/wit-parser/src/resolve/clone.rs | 2 +- crates/wit-parser/src/sizealign.rs | 2 +- 26 files changed, 64 insertions(+), 64 deletions(-) diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 53d30a34b4..7eb3fb52c4 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -682,7 +682,7 @@ impl<'a> TypeEncoder<'a> { } ComponentDefinedType::Future(ty) => self.future(state, *ty), ComponentDefinedType::Stream(ty) => self.stream(state, *ty), - ComponentDefinedType::Error => self.error(state), + ComponentDefinedType::ErrorContext => self.error_context(state), } } @@ -821,9 +821,9 @@ impl<'a> TypeEncoder<'a> { index } - fn error(&self, state: &mut TypeState<'a>) -> u32 { + fn error_context(&self, state: &mut TypeState<'a>) -> u32 { let index = state.cur.encodable.type_count(); - state.cur.encodable.ty().defined_type().error(); + state.cur.encodable.ty().defined_type().error_context(); index } } @@ -1254,7 +1254,7 @@ impl DependencyRegistrar<'_, '_> { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Enum(_) | ComponentDefinedType::Flags(_) - | ComponentDefinedType::Error => {} + | ComponentDefinedType::ErrorContext => {} ComponentDefinedType::List(t) | ComponentDefinedType::Option(t) | ComponentDefinedType::Stream(t) => self.val_type(*t), diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index 3df657c8a1..0c825285bf 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -684,8 +684,8 @@ impl ComponentDefinedTypeEncoder<'_> { payload.encode(self.0); } - /// Define the `error` type. - pub fn error(self) { + /// Define the `error-context` type. + pub fn error_context(self) { self.0.push(0x65); } } diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index 277db62fcb..ce1e1d53cc 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -803,7 +803,7 @@ pub mod component_utils { wasmparser::ComponentDefinedType::Stream(t) => { defined.stream(reencoder.component_val_type(t)); } - wasmparser::ComponentDefinedType::Error => defined.error(), + wasmparser::ComponentDefinedType::ErrorContext => defined.error_context(), } Ok(()) } diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs index 7beea33b7d..512144951b 100644 --- a/crates/wasmparser/src/readers/component/types.rs +++ b/crates/wasmparser/src/readers/component/types.rs @@ -509,8 +509,8 @@ pub enum ComponentDefinedType<'a> { Future(Option), /// A stream type with the specified payload type. Stream(ComponentValType), - /// The error type. - Error, + /// The error-context type. + ErrorContext, } impl<'a> ComponentDefinedType<'a> { @@ -552,7 +552,7 @@ impl<'a> ComponentDefinedType<'a> { 0x68 => ComponentDefinedType::Borrow(reader.read()?), 0x67 => ComponentDefinedType::Future(reader.read()?), 0x66 => ComponentDefinedType::Stream(reader.read()?), - 0x65 => ComponentDefinedType::Error, + 0x65 => ComponentDefinedType::ErrorContext, x => return reader.invalid_leading_byte(x, "component defined type"), }) } diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 3a1898be6a..709e0481c7 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -722,7 +722,7 @@ impl ComponentState { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) | ComponentDefinedType::Enum(_) - | ComponentDefinedType::Error => true, + | ComponentDefinedType::ErrorContext => true, // Referenced types of all these aggregates must all be // named. @@ -3446,7 +3446,7 @@ impl ComponentState { crate::ComponentDefinedType::Stream(ty) => Ok(ComponentDefinedType::Stream( self.create_component_val_type(ty, offset)?, )), - crate::ComponentDefinedType::Error => Ok(ComponentDefinedType::Error), + crate::ComponentDefinedType::ErrorContext => Ok(ComponentDefinedType::ErrorContext), } } diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index b9a8390eb6..823bfb259a 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1045,8 +1045,8 @@ pub enum ComponentDefinedType { Future(Option), /// A stream type with the specified payload type. Stream(ComponentValType), - /// The error type. - Error, + /// The error-context type. + ErrorContext, } impl TypeData for ComponentDefinedType { @@ -1060,7 +1060,7 @@ impl TypeData for ComponentDefinedType { | Self::Own(_) | Self::Future(_) | Self::Stream(_) - | Self::Error => TypeInfo::new(), + | Self::ErrorContext => TypeInfo::new(), Self::Borrow(_) => TypeInfo::borrow(), Self::Record(r) => r.info, Self::Variant(v) => v.info, @@ -1094,7 +1094,7 @@ impl ComponentDefinedType { | Self::Borrow(_) | Self::Future(_) | Self::Stream(_) - | Self::Error => false, + | Self::ErrorContext => false, Self::Option(ty) => ty.contains_ptr(types), Self::Result { ok, err } => { ok.map(|ty| ty.contains_ptr(types)).unwrap_or(false) @@ -1128,7 +1128,7 @@ impl ComponentDefinedType { | Self::Borrow(_) | Self::Future(_) | Self::Stream(_) - | Self::Error => lowered_types.push(ValType::I32), + | Self::ErrorContext => lowered_types.push(ValType::I32), Self::Option(ty) => { Self::push_variant_wasm_types([ty].into_iter(), types, lowered_types) } @@ -1198,7 +1198,7 @@ impl ComponentDefinedType { ComponentDefinedType::Borrow(_) => "borrow", ComponentDefinedType::Future(_) => "future", ComponentDefinedType::Stream(_) => "stream", - ComponentDefinedType::Error => "error", + ComponentDefinedType::ErrorContext => "error-context", } } } @@ -1914,7 +1914,7 @@ impl TypeAlloc { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) | ComponentDefinedType::Enum(_) - | ComponentDefinedType::Error => {} + | ComponentDefinedType::ErrorContext => {} ComponentDefinedType::Record(r) => { for ty in r.fields.values() { self.free_variables_valtype(ty, set); @@ -2053,7 +2053,7 @@ impl TypeAlloc { let ty = &self[id]; match ty { // Primitives are always considered named - ComponentDefinedType::Primitive(_) | ComponentDefinedType::Error => true, + ComponentDefinedType::Primitive(_) | ComponentDefinedType::ErrorContext => true, // These structures are never allowed to be anonymous, so they // themselves must be named. @@ -2241,7 +2241,7 @@ where ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) | ComponentDefinedType::Enum(_) - | ComponentDefinedType::Error => {} + | ComponentDefinedType::ErrorContext => {} ComponentDefinedType::Record(r) => { for ty in r.fields.values_mut() { any_changed |= self.remap_valtype(ty, map); @@ -3220,8 +3220,8 @@ impl<'a> SubtypeCx<'a> { .component_val_type(a, b, offset) .with_context(|| "type mismatch in stream"), (Stream(_), b) => bail!(offset, "expected {}, found stream", b.desc()), - (Error, Error) => Ok(()), - (Error, b) => bail!(offset, "expected {}, found error", b.desc()), + (ErrorContext, ErrorContext) => Ok(()), + (ErrorContext, b) => bail!(offset, "expected {}, found error-context", b.desc()), } } diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 4444f2a65a..51cd636bdc 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -276,7 +276,7 @@ impl Printer<'_, '_> { } ComponentDefinedType::Future(ty) => self.print_future_type(state, *ty)?, ComponentDefinedType::Stream(ty) => self.print_stream_type(state, *ty)?, - ComponentDefinedType::Error => self.result.write_str("error")?, + ComponentDefinedType::ErrorContext => self.result.write_str("error-context")?, } Ok(()) diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 553340622f..69e90079ac 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -323,7 +323,7 @@ impl TypeContents { TypeDefKind::Type(t) => Self::for_type(resolve, t), TypeDefKind::Future(_) => Self::empty(), TypeDefKind::Stream(_) => Self::empty(), - TypeDefKind::Error => Self::empty(), + TypeDefKind::ErrorContext => Self::empty(), TypeDefKind::Unknown => unreachable!(), }, Type::String => Self::STRING, diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 0789ffecaf..1ed436640c 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -155,7 +155,7 @@ pub trait ValtypeEncoder<'a> { TypeDefKind::Type(ty) => self.encode_valtype(resolve, ty)?, TypeDefKind::Future(ty) => self.encode_future(resolve, ty)?, TypeDefKind::Stream(ty) => self.encode_stream(resolve, ty)?, - TypeDefKind::Error => self.encode_error()?, + TypeDefKind::ErrorContext => self.encode_error_context()?, TypeDefKind::Unknown => unreachable!(), TypeDefKind::Resource => { let name = ty.name.as_ref().expect("resources must be named"); @@ -329,9 +329,9 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_error(&mut self) -> Result { + fn encode_error_context(&mut self) -> Result { let (index, encoder) = self.defined_type(); - encoder.error(); + encoder.error_context(); Ok(ComponentValType::Type(index)) } } diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index fca1faf917..4224edddf1 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -540,7 +540,7 @@ impl WitPrinter { TypeDefKind::Stream(_) => { todo!("document has an unnamed stream type") } - TypeDefKind::Error => self.output.push_str("error"), + TypeDefKind::ErrorContext => self.output.push_str("error-context"), TypeDefKind::Unknown => unreachable!(), } } @@ -698,7 +698,7 @@ impl WitPrinter { }, TypeDefKind::Future(_) => panic!("no need to declare futures"), TypeDefKind::Stream(_) => panic!("no need to declare streams"), - TypeDefKind::Error => panic!("no need to declare errors"), + TypeDefKind::ErrorContext => panic!("no need to declare error-contexts"), TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit index 936851fb5b..059722ab86 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/filesystem/types.wit @@ -24,7 +24,7 @@ package wasi:filesystem@0.2.0-rc-2023-11-10; /// /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { - use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream, %error}; + use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream, error}; use wasi:clocks/wall-clock@0.2.0-rc-2023-11-10.{datetime}; /// File size or length of a region within a file. @@ -630,5 +630,5 @@ interface types { /// /// Note that this function is fallible because not all stream-related /// errors are filesystem-related errors. - filesystem-error-code: func(err: borrow<%error>) -> option; + filesystem-error-code: func(err: borrow) -> option; } diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit index b0e8b1ed9a..31918acbb4 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/io/error.wit @@ -1,7 +1,7 @@ package wasi:io@0.2.0-rc-2023-11-10; -interface %error { +interface error { /// A resource which represents some error information. /// /// The only method provided by this resource is `to-debug-string`, @@ -21,7 +21,7 @@ interface %error { /// /// The set of functions which can "downcast" an `error` into a more /// concrete type is open. - resource %error { + resource error { /// Returns a string that is suitable to assist humans in debugging /// this error. /// diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit index 7ccaf0dbac..f6f7fe0e87 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/io/streams.wit @@ -6,7 +6,7 @@ package wasi:io@0.2.0-rc-2023-11-10; /// In the future, the component model is expected to add built-in stream types; /// when it does, they are expected to subsume this API. interface streams { - use %error.{%error}; + use error.{error}; use poll.{pollable}; /// An error for input-stream and output-stream operations. @@ -14,7 +14,7 @@ interface streams { /// The last operation (a write or flush) failed before completion. /// /// More information is available in the `error` payload. - last-operation-failed(%error), + last-operation-failed(error), /// The stream is closed: no more input will be accepted by the /// stream. A closed output-stream will return this error on all /// future operations. diff --git a/crates/wit-component/tests/interfaces/wasi-http/types.wit b/crates/wit-component/tests/interfaces/wasi-http/types.wit index eb613d4fac..0f698e769e 100644 --- a/crates/wit-component/tests/interfaces/wasi-http/types.wit +++ b/crates/wit-component/tests/interfaces/wasi-http/types.wit @@ -4,7 +4,7 @@ interface types { use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10.{duration}; use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream}; - use wasi:io/%error@0.2.0-rc-2023-11-10.{%error as io-error}; + use wasi:io/error@0.2.0-rc-2023-11-10.{error as io-error}; use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; /// This type corresponds to HTTP standard Methods. diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs index 6e68f966cd..fe2b719da4 100644 --- a/crates/wit-encoder/src/from_parser.rs +++ b/crates/wit-encoder/src/from_parser.rs @@ -217,7 +217,7 @@ impl<'a> Converter<'a> { wit_parser::TypeDefKind::Stream(ty) => { TypeDefKind::Type(Type::stream(self.convert_type(ty))) } - wit_parser::TypeDefKind::Error => TypeDefKind::Type(Type::Error), + wit_parser::TypeDefKind::ErrorContext => TypeDefKind::Type(Type::ErrorContext), // all the following are just `type` declarations wit_parser::TypeDefKind::Option(ty) => { let output = Type::option(self.convert_type(ty)); @@ -294,7 +294,7 @@ impl<'a> Converter<'a> { wit_parser::TypeDefKind::Stream(type_) => { Type::stream(self.convert_type(type_)) } - wit_parser::TypeDefKind::Error => Type::Error, + wit_parser::TypeDefKind::ErrorContext => Type::ErrorContext, wit_parser::TypeDefKind::Record(_) | wit_parser::TypeDefKind::Resource | wit_parser::TypeDefKind::Flags(_) diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index 700f089ffe..583e39ab56 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -29,7 +29,7 @@ pub enum Type { Tuple(Tuple), Future(Option>), Stream(Box), - Error, + ErrorContext, Named(Ident), } @@ -126,7 +126,7 @@ impl Display for Type { Type::Stream(type_) => { write!(f, "stream<{type_}>") } - Type::Error => write!(f, "error"), + Type::ErrorContext => write!(f, "error-context"), } } } diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index 4ce7808d17..fee9e1e8ea 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -298,7 +298,7 @@ impl Resolve { result.push(WasmType::I32); } - TypeDefKind::Error => { + TypeDefKind::ErrorContext => { result.push(WasmType::I32); } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 2d11e2e91e..5eb93a6c38 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -741,7 +741,7 @@ enum Type<'a> { Result(Result_<'a>), Future(Future<'a>), Stream(Stream<'a>), - Error(Span), + ErrorContext(Span), } enum Handle<'a> { @@ -1416,7 +1416,7 @@ impl<'a> Type<'a> { } // error - Some((span, Token::Error)) => Ok(Type::Error(span)), + Some((span, Token::ErrorContext)) => Ok(Type::ErrorContext(span)), // own Some((_span, Token::Own)) => { @@ -1464,7 +1464,7 @@ impl<'a> Type<'a> { | Type::F64(span) | Type::Char(span) | Type::String(span) - | Type::Error(span) => *span, + | Type::ErrorContext(span) => *span, Type::Name(id) => id.span, Type::List(l) => l.span, Type::Handle(h) => h.span(), diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index 3a6218a745..ae70352397 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -78,7 +78,7 @@ pub enum Token { Result_, Future, Stream, - Error, + ErrorContext, List, Underscore, As, @@ -298,7 +298,7 @@ impl<'a> Tokenizer<'a> { "result" => Result_, "future" => Future, "stream" => Stream, - "error" => Error, + "error-context" => ErrorContext, "list" => List, "_" => Underscore, "as" => As, @@ -552,7 +552,7 @@ impl Token { Result_ => "keyword `result`", Future => "keyword `future`", Stream => "keyword `stream`", - Error => "keyword `error`", + ErrorContext => "keyword `error-context`", List => "keyword `list`", Underscore => "keyword `_`", Id => "an identifier", diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index ebf7cf54e0..714257308b 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -96,7 +96,7 @@ enum Key { Result(Option, Option), Future(Option), Stream(Type), - Error, + ErrorContext, } enum TypeItem<'a, 'b> { @@ -1256,7 +1256,7 @@ impl<'a> Resolver<'a> { TypeDefKind::Future(self.resolve_optional_type(t.ty.as_deref(), stability)?) } ast::Type::Stream(s) => TypeDefKind::Stream(self.resolve_type(&s.ty, stability)?), - ast::Type::Error(_) => TypeDefKind::Error, + ast::Type::ErrorContext(_) => TypeDefKind::ErrorContext, }) } @@ -1359,7 +1359,7 @@ impl<'a> Resolver<'a> { TypeDefKind::Result(r) => Key::Result(r.ok, r.err), TypeDefKind::Future(ty) => Key::Future(*ty), TypeDefKind::Stream(ty) => Key::Stream(*ty), - TypeDefKind::Error => Key::Error, + TypeDefKind::ErrorContext => Key::ErrorContext, TypeDefKind::Unknown => unreachable!(), }; let id = self.anon_types.entry(key).or_insert_with(|| { @@ -1545,7 +1545,7 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { | ast::Type::String(_) | ast::Type::Flags(_) | ast::Type::Enum(_) - | ast::Type::Error(_) => {} + | ast::Type::ErrorContext(_) => {} ast::Type::Name(name) => deps.push(name.clone()), ast::Type::Handle(handle) => match handle { ast::Handle::Own { resource } => deps.push(resource.clone()), diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index b69f07c855..ec25635baa 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -1263,7 +1263,7 @@ impl WitPackageDecoder<'_> { | TypeDefKind::Handle(_) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) - | TypeDefKind::Error => {} + | TypeDefKind::ErrorContext => {} TypeDefKind::Resource | TypeDefKind::Record(_) @@ -1401,7 +1401,7 @@ impl WitPackageDecoder<'_> { ComponentDefinedType::Stream(ty) => Ok(TypeDefKind::Stream(self.convert_valtype(ty)?)), - ComponentDefinedType::Error => Ok(TypeDefKind::Error), + ComponentDefinedType::ErrorContext => Ok(TypeDefKind::ErrorContext), } } @@ -1699,7 +1699,7 @@ impl Registrar<'_> { | ComponentDefinedType::Enum(_) | ComponentDefinedType::Own(_) | ComponentDefinedType::Borrow(_) - | ComponentDefinedType::Error => Ok(()), + | ComponentDefinedType::ErrorContext => Ok(()), } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index a2cf8bffa8..e4a8fd6d94 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -570,7 +570,7 @@ pub enum TypeDefKind { List(Type), Future(Option), Stream(Type), - Error, + ErrorContext, Type(Type), /// This represents a type of unknown structure imported from a foreign @@ -599,7 +599,7 @@ impl TypeDefKind { TypeDefKind::List(_) => "list", TypeDefKind::Future(_) => "future", TypeDefKind::Stream(_) => "stream", - TypeDefKind::Error => "error", + TypeDefKind::ErrorContext => "error-context", TypeDefKind::Type(_) => "type", TypeDefKind::Unknown => "unknown", } @@ -1020,7 +1020,7 @@ fn find_futures_and_streams(resolve: &Resolve, ty: Type, results: &mut Vec {} + | TypeDefKind::ErrorContext => {} TypeDefKind::Record(r) => { for Field { ty, .. } in &r.fields { find_futures_and_streams(resolve, *ty, results); diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index 6c68106b86..84e2ebf8b7 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -161,7 +161,7 @@ pub trait TypeIdVisitor { self.visit_type(resolve, ty); } } - TypeDefKind::Error + TypeDefKind::ErrorContext | TypeDefKind::Flags(_) | TypeDefKind::Enum(_) | TypeDefKind::Future(None) => {} diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index ba7a59ead5..02f2932b6e 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -573,7 +573,7 @@ package {name} is defined in two different locations:\n\ | TypeDefKind::Result(_) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) - | TypeDefKind::Error => false, + | TypeDefKind::ErrorContext => false, TypeDefKind::Type(t) => self.all_bits_valid(t), TypeDefKind::Handle(h) => match h { @@ -2996,7 +2996,7 @@ impl Remap { } } Future(Some(t)) => self.update_ty(resolve, t, span)?, - Error => {} + ErrorContext => {} // Note that `update_ty` is specifically not used here as typedefs // because for the `type a = b` form that doesn't force `a` to be a @@ -3395,7 +3395,7 @@ impl Remap { .iter() .filter_map(|t| t.as_ref()) .any(|t| self.type_has_borrow(resolve, t)), - TypeDefKind::Future(None) | TypeDefKind::Error => false, + TypeDefKind::Future(None) | TypeDefKind::ErrorContext => false, TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index 6deec56ac8..4c8c18ef44 100644 --- a/crates/wit-parser/src/resolve/clone.rs +++ b/crates/wit-parser/src/resolve/clone.rs @@ -106,7 +106,7 @@ impl<'a> Cloner<'a> { | TypeDefKind::Resource | TypeDefKind::Flags(_) | TypeDefKind::Enum(_) - | TypeDefKind::Error => {} + | TypeDefKind::ErrorContext => {} TypeDefKind::Handle(Handle::Own(ty) | Handle::Borrow(ty)) => { self.type_id(ty); } diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index 8631db5cae..f73dd9e88a 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -259,7 +259,7 @@ impl SizeAlign { TypeDefKind::Handle(_) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) - | TypeDefKind::Error => int_size_align(Int::U32), + | TypeDefKind::ErrorContext => int_size_align(Int::U32), // This shouldn't be used for anything since raw resources aren't part of the ABI -- just handles to // them. TypeDefKind::Resource => ElementInfo::new( From 940de62bd278831d1134bde84caedc1adcce6a00 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 6 Nov 2024 10:49:41 -0700 Subject: [PATCH 5/5] rename `error` to `error-context` per latest spec (part 2) Also, parse string encoding and realloc from encoded `error-context.new` and `error-context.debug-string` names. Signed-off-by: Joel Dice --- crates/wasm-encoder/src/component/builder.rs | 19 ++-- .../wasm-encoder/src/component/canonicals.rs | 17 ++-- crates/wasm-encoder/src/reencode/component.rs | 14 +-- .../src/readers/component/canonicals.rs | 18 ++-- crates/wasmparser/src/validator.rs | 6 +- crates/wasmprinter/src/component.rs | 12 +-- crates/wit-component/src/encoding.rs | 89 +++++++++++------- crates/wit-component/src/metadata.rs | 18 ---- crates/wit-component/src/validation.rs | 93 ++++++++++++++----- src/bin/wasm-tools/dump.rs | 8 +- 10 files changed, 174 insertions(+), 120 deletions(-) diff --git a/crates/wasm-encoder/src/component/builder.rs b/crates/wasm-encoder/src/component/builder.rs index b0fab33083..bd136d09bf 100644 --- a/crates/wasm-encoder/src/component/builder.rs +++ b/crates/wasm-encoder/src/component/builder.rs @@ -520,29 +520,30 @@ impl ComponentBuilder { inc(&mut self.core_funcs) } - /// Declares a new `error.new` intrinsic. - pub fn error_new(&mut self, options: O) -> u32 + /// Declares a new `error-context.new` intrinsic. + pub fn error_context_new(&mut self, options: O) -> u32 where O: IntoIterator, O::IntoIter: ExactSizeIterator, { - self.canonical_functions().error_new(options); + self.canonical_functions().error_context_new(options); inc(&mut self.core_funcs) } - /// Declares a new `error.debug-message` intrinsic. - pub fn error_debug_message(&mut self, options: O) -> u32 + /// Declares a new `error-context.debug-message` intrinsic. + pub fn error_context_debug_message(&mut self, options: O) -> u32 where O: IntoIterator, O::IntoIter: ExactSizeIterator, { - self.canonical_functions().error_debug_message(options); + self.canonical_functions() + .error_context_debug_message(options); inc(&mut self.core_funcs) } - /// Declares a new `error.drop` intrinsic. - pub fn error_drop(&mut self) -> u32 { - self.canonical_functions().error_drop(); + /// Declares a new `error-context.drop` intrinsic. + pub fn error_context_drop(&mut self) -> u32 { + self.canonical_functions().error_context_drop(); inc(&mut self.core_funcs) } diff --git a/crates/wasm-encoder/src/component/canonicals.rs b/crates/wasm-encoder/src/component/canonicals.rs index 748c672898..867cfdb227 100644 --- a/crates/wasm-encoder/src/component/canonicals.rs +++ b/crates/wasm-encoder/src/component/canonicals.rs @@ -399,9 +399,9 @@ impl CanonicalFunctionSection { self } - /// Defines a function to create a new `error` with a specified debug - /// message. - pub fn error_new(&mut self, options: O) -> &mut Self + /// Defines a function to create a new `error-context` with a specified + /// debug message. + pub fn error_context_new(&mut self, options: O) -> &mut Self where O: IntoIterator, O::IntoIter: ExactSizeIterator, @@ -416,11 +416,12 @@ impl CanonicalFunctionSection { self } - /// Defines a function to get the debug message for a specified `error`. + /// Defines a function to get the debug message for a specified + /// `error-context`. /// /// Note that the debug message might not necessarily match what was passed - /// to `error.new`. - pub fn error_debug_message(&mut self, options: O) -> &mut Self + /// to `error-context.new`. + pub fn error_context_debug_message(&mut self, options: O) -> &mut Self where O: IntoIterator, O::IntoIter: ExactSizeIterator, @@ -435,8 +436,8 @@ impl CanonicalFunctionSection { self } - /// Defines a function to drop a specified `error`. - pub fn error_drop(&mut self) -> &mut Self { + /// Defines a function to drop a specified `error-context`. + pub fn error_context_drop(&mut self) -> &mut Self { self.bytes.push(0x1e); self.num_added += 1; self diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index ce1e1d53cc..8298dd5668 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -1036,14 +1036,16 @@ pub mod component_utils { wasmparser::CanonicalFunction::FutureCloseWritable { ty } => { section.future_close_writable(reencoder.component_type_index(ty)); } - wasmparser::CanonicalFunction::ErrorNew { options } => { - section.error_new(options.iter().map(|o| reencoder.canonical_option(*o))); + wasmparser::CanonicalFunction::ErrorContextNew { options } => { + section.error_context_new(options.iter().map(|o| reencoder.canonical_option(*o))); } - wasmparser::CanonicalFunction::ErrorDebugMessage { options } => { - section.error_debug_message(options.iter().map(|o| reencoder.canonical_option(*o))); + wasmparser::CanonicalFunction::ErrorContextDebugMessage { options } => { + section.error_context_debug_message( + options.iter().map(|o| reencoder.canonical_option(*o)), + ); } - wasmparser::CanonicalFunction::ErrorDrop => { - section.error_drop(); + wasmparser::CanonicalFunction::ErrorContextDrop => { + section.error_context_drop(); } } Ok(()) diff --git a/crates/wasmparser/src/readers/component/canonicals.rs b/crates/wasmparser/src/readers/component/canonicals.rs index d14a447ad0..d2ebe33ab6 100644 --- a/crates/wasmparser/src/readers/component/canonicals.rs +++ b/crates/wasmparser/src/readers/component/canonicals.rs @@ -212,22 +212,22 @@ pub enum CanonicalFunction { /// The `future` type to expect. ty: u32, }, - /// A function to create a new `error` with a specified debug + /// A function to create a new `error-context` with a specified debug /// message. - ErrorNew { + ErrorContextNew { /// String encoding, memory, etc. to use when loading debug message. options: Box<[CanonicalOption]>, }, - /// A function to get the debug message for a specified `error`. + /// A function to get the debug message for a specified `error-context`. /// /// Note that the debug message might not necessarily match what was passed /// to `error.new`. - ErrorDebugMessage { + ErrorContextDebugMessage { /// String encoding, memory, etc. to use when storing debug message. options: Box<[CanonicalOption]>, }, - /// A function to drop a specified `error`. - ErrorDrop, + /// A function to drop a specified `error-context`. + ErrorContextDrop, } /// A reader for the canonical section of a WebAssembly component. @@ -335,17 +335,17 @@ impl<'a> FromReader<'a> for CanonicalFunction { }, 0x1a => CanonicalFunction::FutureCloseReadable { ty: reader.read()? }, 0x1b => CanonicalFunction::FutureCloseWritable { ty: reader.read()? }, - 0x1c => CanonicalFunction::ErrorNew { + 0x1c => CanonicalFunction::ErrorContextNew { options: reader .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? .collect::>()?, }, - 0x1d => CanonicalFunction::ErrorDebugMessage { + 0x1d => CanonicalFunction::ErrorContextDebugMessage { options: reader .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? .collect::>()?, }, - 0x1e => CanonicalFunction::ErrorDrop, + 0x1e => CanonicalFunction::ErrorContextDrop, x => return reader.invalid_leading_byte(x, "canonical function"), }) } diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index c4b9db51db..7c1b714fd1 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1370,13 +1370,13 @@ impl Validator { crate::CanonicalFunction::FutureCloseWritable { ty } => { current.future_close_writable(ty, types, offset, features) } - crate::CanonicalFunction::ErrorNew { options } => { + crate::CanonicalFunction::ErrorContextNew { options } => { current.error_new(options.into_vec(), types, offset, features) } - crate::CanonicalFunction::ErrorDebugMessage { options } => { + crate::CanonicalFunction::ErrorContextDebugMessage { options } => { current.error_debug_message(options.into_vec(), types, offset, features) } - crate::CanonicalFunction::ErrorDrop => { + crate::CanonicalFunction::ErrorContextDrop => { current.error_drop(types, offset, features) } } diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 51cd636bdc..afa3d64823 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -1142,31 +1142,31 @@ impl Printer<'_, '_> { self.end_group()?; state.core.funcs += 1; } - CanonicalFunction::ErrorNew { options } => { + CanonicalFunction::ErrorContextNew { options } => { self.start_group("core func ")?; self.print_name(&state.core.func_names, state.core.funcs)?; self.result.write_str(" ")?; - self.start_group("canon error.new ")?; + self.start_group("canon error-context.new ")?; self.print_canonical_options(state, &options)?; self.end_group()?; self.end_group()?; state.core.funcs += 1; } - CanonicalFunction::ErrorDebugMessage { options } => { + CanonicalFunction::ErrorContextDebugMessage { options } => { self.start_group("core func ")?; self.print_name(&state.core.func_names, state.core.funcs)?; self.result.write_str(" ")?; - self.start_group("canon error.debug-message ")?; + self.start_group("canon error-context.debug-message ")?; self.print_canonical_options(state, &options)?; self.end_group()?; self.end_group()?; state.core.funcs += 1; } - CanonicalFunction::ErrorDrop => { + CanonicalFunction::ErrorContextDrop => { self.start_group("core func ")?; self.print_name(&state.core.func_names, state.core.funcs)?; self.result.write_str(" ")?; - self.start_group("canon error.drop")?; + self.start_group("canon error-context.drop")?; self.end_group()?; self.end_group()?; state.core.funcs += 1; diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 69e90079ac..cfd07970eb 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -1380,31 +1380,35 @@ impl<'a> EncodingState<'a> { ShimKind::TaskPoll { async_ } => self .component .task_poll(*async_, self.memory_index.unwrap()), - ShimKind::ErrorNew { for_module } | ShimKind::ErrorDebugMessage { for_module } => { - let metadata = self.info.module_metadata_for(*for_module); - let exports = self.info.exports_for(*for_module); - let instance_index = self.instance_for(*for_module); - let encoding = metadata.general_purpose_encoding(); - let realloc = exports.general_purpose_realloc(); - let realloc_index = realloc - .map(|name| self.core_alias_export(instance_index, name, ExportKind::Func)); - - match &shim.kind { - ShimKind::ErrorNew { .. } => self.component.error_new( - (RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING) - .into_iter(encoding, self.memory_index, realloc_index)? - .collect::>(), - ), - ShimKind::ErrorDebugMessage { .. } => self.component.error_debug_message( + ShimKind::ErrorContextNew { + for_module, + encoding, + } + | ShimKind::ErrorContextDebugMessage { + for_module, + encoding, + .. + } => match &shim.kind { + ShimKind::ErrorContextNew { .. } => self.component.error_context_new( + (RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING) + .into_iter(*encoding, self.memory_index, None)? + .collect::>(), + ), + ShimKind::ErrorContextDebugMessage { realloc, .. } => { + let instance_index = self.instance_for(*for_module); + let realloc_index = + Some(self.core_alias_export(instance_index, realloc, ExportKind::Func)); + + self.component.error_context_debug_message( (RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING | RequiredOptions::REALLOC) - .into_iter(encoding, self.memory_index, realloc_index)? + .into_iter(*encoding, self.memory_index, realloc_index)? .collect::>(), - ), - _ => unreachable!(), + ) } - } + _ => unreachable!(), + }, }; exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); @@ -1802,26 +1806,35 @@ impl<'a> EncodingState<'a> { let index = self.component.future_close_writable(type_index); return Ok((ExportKind::Func, index)); } - Import::ErrorNew => { + Import::ErrorContextNew { encoding } => { let index = self.component.core_alias_export( self.shim_instance_index .expect("shim should be instantiated"), - &shims.shims[&ShimKind::ErrorNew { for_module }].name, + &shims.shims[&ShimKind::ErrorContextNew { + for_module, + encoding: *encoding, + }] + .name, ExportKind::Func, ); return Ok((ExportKind::Func, index)); } - Import::ErrorDebugMessage => { + Import::ErrorContextDebugMessage { encoding, realloc } => { let index = self.component.core_alias_export( self.shim_instance_index .expect("shim should be instantiated"), - &shims.shims[&ShimKind::ErrorDebugMessage { for_module }].name, + &shims.shims[&ShimKind::ErrorContextDebugMessage { + for_module, + encoding: *encoding, + realloc, + }] + .name, ExportKind::Func, ); return Ok((ExportKind::Func, index)); } - Import::ErrorDrop => { - let index = self.component.error_drop(); + Import::ErrorContextDrop => { + let index = self.component.error_context_drop(); return Ok((ExportKind::Func, index)); } Import::WorldFunc(key, name, abi) => (key, name, None, *abi), @@ -2059,11 +2072,14 @@ enum ShimKind<'a> { TaskPoll { async_: bool, }, - ErrorNew { + ErrorContextNew { for_module: CustomModule<'a>, + encoding: StringEncoding, }, - ErrorDebugMessage { + ErrorContextDebugMessage { for_module: CustomModule<'a>, + encoding: StringEncoding, + realloc: &'a str, }, } @@ -2135,7 +2151,7 @@ impl<'a> Shims<'a> { | Import::ExportedResourceDrop(..) | Import::ExportedResourceRep(..) | Import::ExportedResourceNew(..) - | Import::ErrorDrop + | Import::ErrorContextDrop | Import::TaskBackpressure | Import::TaskYield { .. } | Import::SubtaskDrop @@ -2234,13 +2250,16 @@ impl<'a> Shims<'a> { continue; } - Import::ErrorNew => { + Import::ErrorContextNew { encoding } => { let name = self.shims.len().to_string(); self.push(Shim { name, debug_name: "error-new".to_string(), options: RequiredOptions::empty(), - kind: ShimKind::ErrorNew { for_module }, + kind: ShimKind::ErrorContextNew { + for_module, + encoding: *encoding, + }, sig: WasmSignature { params: vec![WasmType::I32; 2], results: vec![WasmType::I32], @@ -2251,13 +2270,17 @@ impl<'a> Shims<'a> { continue; } - Import::ErrorDebugMessage => { + Import::ErrorContextDebugMessage { encoding, realloc } => { let name = self.shims.len().to_string(); self.push(Shim { name, debug_name: "error-debug-message".to_string(), options: RequiredOptions::empty(), - kind: ShimKind::ErrorDebugMessage { for_module }, + kind: ShimKind::ErrorContextDebugMessage { + for_module, + encoding: *encoding, + realloc, + }, sig: WasmSignature { params: vec![WasmType::I32; 2], results: vec![], diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index e835d1f84b..7eaf5a68d4 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -116,8 +116,6 @@ pub struct ModuleMetadata { /// Per-function options exported from the core wasm module, currently only /// related to string encoding. pub export_encodings: EncodingMap, - - general_purpose_encoding: StringEncoding, } /// Internal map that keeps track of encodings for various world imports and @@ -404,7 +402,6 @@ impl Bindgen { ModuleMetadata { import_encodings, export_encodings, - general_purpose_encoding, }, producers, } = other; @@ -421,13 +418,6 @@ impl Bindgen { self.metadata.import_encodings.merge(import_encodings)?; self.metadata.export_encodings.merge(export_encodings)?; - if self.metadata.general_purpose_encoding != general_purpose_encoding { - bail!( - "string encoding mismatch: {:?} vs {:?}", - self.metadata.general_purpose_encoding, - general_purpose_encoding - ); - } if let Some(producers) = producers { if let Some(mine) = &mut self.producers { mine.merge(&producers); @@ -452,14 +442,6 @@ impl ModuleMetadata { ret.import_encodings .insert_all(resolve, &world.imports, encoding); - ret.general_purpose_encoding = encoding; - ret } - - /// String encoding to use for passing strings to and from built-in - /// functions such as `error.new`, `error.debug-string`, etc. - pub fn general_purpose_encoding(&self) -> StringEncoding { - self.general_purpose_encoding - } } diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index a4ea755079..7de204b4c8 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -1,5 +1,5 @@ use crate::encoding::{Instance, Item, LibraryInfo, MainOrAdapter}; -use crate::ComponentEncoder; +use crate::{ComponentEncoder, StringEncoding}; use anyhow::{bail, Context, Result}; use indexmap::{map::Entry, IndexMap, IndexSet}; use std::hash::{Hash, Hasher}; @@ -257,9 +257,14 @@ pub enum Import { }, FutureCloseReadable(TypeId), FutureCloseWritable(TypeId), - ErrorNew, - ErrorDebugMessage, - ErrorDrop, + ErrorContextNew { + encoding: StringEncoding, + }, + ErrorContextDebugMessage { + encoding: StringEncoding, + realloc: String, + }, + ErrorContextDrop, } impl ImportMap { @@ -434,10 +439,10 @@ impl ImportMap { }; if module == names.import_root() { - if Some(name) == names.error_drop() { + if Some(name) == names.error_context_drop() { let expected = FuncType::new([ValType::I32], []); validate_func_sig(name, &expected, ty)?; - return Ok(Import::ErrorDrop); + return Ok(Import::ErrorContextDrop); } if Some(name) == names.task_backpressure() { @@ -476,16 +481,19 @@ impl ImportMap { return Ok(Import::SubtaskDrop); } - if Some(name) == names.error_new() { + if let Some(encoding) = names.error_context_new(name) { let expected = FuncType::new([ValType::I32; 2], [ValType::I32]); validate_func_sig(name, &expected, ty)?; - return Ok(Import::ErrorNew); + return Ok(Import::ErrorContextNew { encoding }); } - if Some(name) == names.error_debug_message() { + if let Some((encoding, realloc)) = names.error_context_debug_message(name) { let expected = FuncType::new([ValType::I32; 2], []); validate_func_sig(name, &expected, ty)?; - return Ok(Import::ErrorDebugMessage); + return Ok(Import::ErrorContextDebugMessage { + encoding, + realloc: realloc.to_owned(), + }); } if let Some(import) = async_import(None)? { @@ -927,9 +935,7 @@ impl ExportMap { self.general_purpose_realloc() } - /// Returns the realloc to use when calling built-in functions such as - /// `error.new`, `error.debug-string`, etc. - pub fn general_purpose_realloc(&self) -> Option<&str> { + fn general_purpose_realloc(&self) -> Option<&str> { self.find(|m| matches!(m, Export::GeneralPurposeRealloc)) } @@ -1059,9 +1065,9 @@ trait NameMangling { fn subtask_drop(&self) -> Option<&str>; fn callback_name<'a>(&self, s: &'a str) -> Option<&'a str>; fn async_name<'a>(&self, s: &'a str) -> Option<&'a str>; - fn error_new(&self) -> Option<&str>; - fn error_debug_message(&self) -> Option<&str>; - fn error_drop(&self) -> Option<&str>; + fn error_context_new(&self, s: &str) -> Option; + fn error_context_debug_message<'a>(&self, s: &'a str) -> Option<(StringEncoding, &'a str)>; + fn error_context_drop(&self) -> Option<&str>; fn payload_import( &self, module: &str, @@ -1154,13 +1160,15 @@ impl NameMangling for Standard { _ = s; None } - fn error_new(&self) -> Option<&str> { + fn error_context_new(&self, s: &str) -> Option { + _ = s; None } - fn error_debug_message(&self) -> Option<&str> { + fn error_context_debug_message<'a>(&self, s: &'a str) -> Option<(StringEncoding, &'a str)> { + _ = s; None } - fn error_drop(&self) -> Option<&str> { + fn error_context_drop(&self) -> Option<&str> { None } fn payload_import( @@ -1333,14 +1341,40 @@ impl NameMangling for Legacy { fn async_name<'a>(&self, s: &'a str) -> Option<&'a str> { s.strip_prefix("[async]") } - fn error_new(&self) -> Option<&str> { - Some("[error-new]") + fn error_context_new(&self, s: &str) -> Option { + parse_encoding( + s.strip_prefix("[error-context-new;encoding=")? + .strip_suffix("]")?, + ) } - fn error_debug_message(&self) -> Option<&str> { - Some("[error-debug-message]") + fn error_context_debug_message<'a>(&self, s: &'a str) -> Option<(StringEncoding, &'a str)> { + let mut suffix = s.strip_prefix("[error-context-debug-message;")?; + let mut encoding = None; + let mut realloc = None; + loop { + if let Some(index) = suffix.find(';').or_else(|| suffix.find(']')) { + if let Some(suffix) = suffix[..index].strip_prefix("encoding=") { + if encoding.is_some() { + return None; + } + encoding = parse_encoding(suffix) + } else if let Some(suffix) = suffix[..index].strip_prefix("realloc=") { + if realloc.is_some() { + return None; + } + realloc = Some(suffix); + } else { + return None; + } + suffix = &suffix[index + 1..]; + } else { + break; + } + } + Some((encoding?, realloc?)) } - fn error_drop(&self) -> Option<&str> { - Some("[error-drop]") + fn error_context_drop(&self) -> Option<&str> { + Some("[error-context-drop]") } fn payload_import( &self, @@ -1859,3 +1893,12 @@ fn get_function( }; Ok(function) } + +fn parse_encoding(s: &str) -> Option { + match s { + "utf8" => Some(StringEncoding::UTF8), + "utf16" => Some(StringEncoding::UTF16), + "compact-utf16" => Some(StringEncoding::CompactUTF16), + _ => None, + } +} diff --git a/src/bin/wasm-tools/dump.rs b/src/bin/wasm-tools/dump.rs index 94f22f4127..480a4ba6df 100644 --- a/src/bin/wasm-tools/dump.rs +++ b/src/bin/wasm-tools/dump.rs @@ -442,9 +442,11 @@ impl<'a> Dump<'a> { | CanonicalFunction::FutureCancelWrite { .. } | CanonicalFunction::FutureCloseReadable { .. } | CanonicalFunction::FutureCloseWritable { .. } - | CanonicalFunction::ErrorNew { .. } - | CanonicalFunction::ErrorDebugMessage { .. } - | CanonicalFunction::ErrorDrop => ("core func", &mut i.core_funcs), + | CanonicalFunction::ErrorContextNew { .. } + | CanonicalFunction::ErrorContextDebugMessage { .. } + | CanonicalFunction::ErrorContextDrop => { + ("core func", &mut i.core_funcs) + } }; write!(me.state, "[{} {}] {:?}", name, inc(col), f)?;