diff --git a/crates/wasm-compose/Cargo.toml b/crates/wasm-compose/Cargo.toml index 5fcf1aaafe..6c29798212 100644 --- a/crates/wasm-compose/Cargo.toml +++ b/crates/wasm-compose/Cargo.toml @@ -17,7 +17,7 @@ workspace = true [dependencies] wat = { workspace = true } wasm-encoder = { workspace = true, features = ['wasmparser', 'component-model'] } -wasmparser = { workspace = true, features = ['validate', 'component-model'] } +wasmparser = { workspace = true, features = ['validate', 'component-model', 'features'] } indexmap = { workspace = true, features = ["serde"] } anyhow = { workspace = true } serde = { workspace = true } 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..7eb3fb52c4 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::ErrorContext => self.error_context(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_context(&self, state: &mut TypeState<'a>) -> u32 { + let index = state.cur.encodable.type_count(); + state.cur.encodable.ty().defined_type().error_context(); + 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::ErrorContext => {} + 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..99f17aec72 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,7 @@ 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::all()); let mut imports = IndexMap::new(); let mut exports = IndexMap::new(); @@ -439,7 +439,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 +448,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 +508,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 +556,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 +586,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 +601,7 @@ impl<'a> CompositionGraph<'a> { } for resources in resource_imports.values() { - match &resources[..] { + match &resources.iter().copied().collect::>()[..] { [] => unreachable!(), [_] => {} [first, rest @ ..] => { @@ -653,10 +657,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 +708,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 c5d2deebf4..fedff5faa6 100644 --- a/crates/wasm-encoder/src/component/builder.rs +++ b/crates/wasm-encoder/src/component/builder.rs @@ -317,7 +317,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) @@ -385,6 +385,169 @@ 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_write(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_write(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-context.new` intrinsic. + pub fn error_context_new(&mut self, options: O) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + self.canonical_functions().error_context_new(options); + inc(&mut self.core_funcs) + } + + /// 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_context_debug_message(options); + inc(&mut self.core_funcs) + } + + /// 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) + } + /// 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 3407d5dd89..e85175511e 100644 --- a/crates/wasm-encoder/src/component/canonicals.rs +++ b/crates/wasm-encoder/src/component/canonicals.rs @@ -22,6 +22,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 { @@ -42,6 +47,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); + } } } } @@ -162,6 +174,275 @@ 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-context` with a specified + /// debug message. + pub fn error_context_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-context`. + /// + /// Note that the debug message might not necessarily match what was passed + /// to `error-context.new`. + pub fn error_context_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-context`. + pub fn error_context_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 ccb45447c4..44a877db2c 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -672,6 +672,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-context` type. + pub fn error_context(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 f8135e6822..1ac94d68ca 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -800,6 +800,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::ErrorContext => defined.error_context(), } Ok(()) } @@ -960,6 +967,89 @@ 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::ErrorContextNew { options } => { + section.error_context_new(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::ErrorContextDrop => { + section.error_context_drop(); + } } Ok(()) } @@ -1242,6 +1332,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 36d2c4b997..53661b71be 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..47a54deece 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), + _ => Err(BinaryReaderError::new( + "invalid boolean value", + 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..d2ebe33ab6 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-context` with a specified debug + /// message. + 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-context`. + /// + /// Note that the debug message might not necessarily match what was passed + /// to `error.new`. + ErrorContextDebugMessage { + /// String encoding, memory, etc. to use when storing debug message. + options: Box<[CanonicalOption]>, + }, + /// A function to drop a specified `error-context`. + ErrorContextDrop, } /// 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::ErrorContextNew { + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x1d => CanonicalFunction::ErrorContextDebugMessage { + options: reader + .read_iter(MAX_WASM_CANONICAL_OPTIONS, "canonical options")? + .collect::>()?, + }, + 0x1e => CanonicalFunction::ErrorContextDrop, 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..512144951b 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-context type. + ErrorContext, } 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::ErrorContext, 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..182ade9c4f 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,74 @@ 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, types, 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::ErrorContextNew { options } => { + current.error_context_new(options.into_vec(), types, offset, features) + } + crate::CanonicalFunction::ErrorContextDebugMessage { options } => current + .error_context_debug_message(options.into_vec(), types, offset, features), + crate::CanonicalFunction::ErrorContextDrop => { + current.error_context_drop(types, offset, features) + } } }, ) diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 237e7994b4..ed70e5970b 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -3,7 +3,7 @@ use super::{ check_max, component_types::{ - AliasableResourceId, ComponentAnyTypeId, ComponentCoreInstanceTypeId, + Abi, AliasableResourceId, ComponentAnyTypeId, ComponentCoreInstanceTypeId, ComponentCoreModuleTypeId, ComponentCoreTypeId, ComponentDefinedType, ComponentDefinedTypeId, ComponentEntityType, ComponentFuncType, ComponentFuncTypeId, ComponentInstanceType, ComponentInstanceTypeId, ComponentType, ComponentTypeId, @@ -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, 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::ErrorContext => 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,37 @@ 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, + if options.contains(&CanonicalOption::Async) { + if options + .iter() + .any(|v| matches!(v, CanonicalOption::Callback(_))) + { + Abi::LiftAsync + } else { + Abi::LiftAsyncStackful + } + } else { + Abi::LiftSync + }, + ); + self.check_options( + Some(core_ty), + &info, + &options, + types, + offset, + features, + true, + )?; if core_ty.params() != info.params.as_slice() { bail!( @@ -990,17 +1020,24 @@ 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, + if options.contains(&CanonicalOption::Async) { + Abi::LowerAsync + } else { + Abi::LowerSync + }, + ); - 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); + let id = types.intern_func_type(info.into_func_type(), offset); self.core_funcs.push(id); Ok(()) @@ -1013,9 +1050,7 @@ impl ComponentState { offset: usize, ) -> Result<()> { let rep = self.check_local_resource(resource, types, offset)?; - let func_ty = FuncType::new([rep], [ValType::I32]); - let core_ty = SubType::func(func_ty, false); - let id = types.intern_sub_type(core_ty, offset); + let id = types.intern_func_type(FuncType::new([rep], [ValType::I32]), offset); self.core_funcs.push(id); Ok(()) } @@ -1027,9 +1062,7 @@ impl ComponentState { offset: usize, ) -> Result<()> { self.resource_at(resource, types, offset)?; - let func_ty = FuncType::new([ValType::I32], []); - let core_ty = SubType::func(func_ty, false); - let id = types.intern_sub_type(core_ty, offset); + let id = types.intern_func_type(FuncType::new([ValType::I32], []), offset); self.core_funcs.push(id); Ok(()) } @@ -1041,13 +1074,573 @@ impl ComponentState { offset: usize, ) -> Result<()> { let rep = self.check_local_resource(resource, types, offset)?; - let func_ty = FuncType::new([ValType::I32], [rep]); - let core_ty = SubType::func(func_ty, false); - let id = types.intern_sub_type(core_ty, offset); + let id = types.intern_func_type(FuncType::new([ValType::I32], [rep]), offset); + self.core_funcs.push(id); + 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" + ) + } + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); + Ok(()) + } + + pub fn task_return( + &mut self, + type_index: u32, + types: &mut TypeAlloc, + 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)?; + let Some(SubType { + composite_type: + CompositeType { + inner: CompositeInnerType::Func(_), + .. + }, + .. + }) = types.get(id) + else { + bail!(offset, "invalid `task.return` type index"); + }; + 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)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + 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)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + 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" + ) + } + + self.core_funcs + .push(types.intern_func_type(FuncType::new([], []), offset)); + 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" + ) + } + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Stream(_) = &types[ty] else { + bail!(offset, "`stream.new` requires a stream type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([], [ValType::I32]), offset)); + 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 ComponentDefinedType::Stream(payload_type) = &types[ty] else { + bail!(offset, "`stream.read` requires a stream type") + }; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = payload_type.contains_ptr(types); + self.check_options(None, &info, &options, types, offset, features, true)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 3], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Stream(_) = &types[ty] else { + bail!(offset, "`stream.write` requires a stream type") + }; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = false; + self.check_options(None, &info, &options, types, offset, features, true)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 3], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Stream(_) = &types[ty] else { + bail!(offset, "`stream.cancel-read` requires a stream type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Stream(_) = &types[ty] else { + bail!(offset, "`stream.cancel-write` requires a stream type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Stream(_) = &types[ty] else { + bail!(offset, "`stream.close-readable` requires a stream type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Stream(_) = &types[ty] else { + bail!(offset, "`stream.close-writable` requires a stream type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 2], []), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Future(_) = &types[ty] else { + bail!(offset, "`future.new` requires a future type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([], [ValType::I32]), offset)); + 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 ComponentDefinedType::Future(payload_type) = &types[ty] else { + bail!(offset, "`future.read` requires a future type") + }; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = payload_type + .map(|ty| ty.contains_ptr(types)) + .unwrap_or(false); + self.check_options(None, &info, &options, types, offset, features, true)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 2], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Future(_) = &types[ty] else { + bail!(offset, "`future.write` requires a future type") + }; + + let mut info = LoweringInfo::default(); + info.requires_memory = true; + info.requires_realloc = false; + self.check_options(None, &info, &options, types, offset, features, true)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 2], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Future(_) = &types[ty] else { + bail!(offset, "`future.cancel-read` requires a future type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Future(_) = &types[ty] else { + bail!(offset, "`future.cancel-write` requires a future type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + 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" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Future(_) = &types[ty] else { + bail!(offset, "`future.close-readable` requires a future type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); + 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.close-writable` requires the component model async feature" + ) + } + + let ty = self.defined_type_at(ty, offset)?; + let ComponentDefinedType::Future(_) = &types[ty] else { + bail!(offset, "`future.close-writable` requires a future type") + }; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 2], []), offset)); + Ok(()) + } + + pub fn error_context_new( + &mut self, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`error-context.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)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 2], [ValType::I32]), offset)); + Ok(()) + } + + pub fn error_context_debug_message( + &mut self, + options: Vec, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`error-context.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)?; + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32; 2], []), offset)); + Ok(()) + } + + pub fn error_context_drop( + &mut self, + types: &mut TypeAlloc, + offset: usize, + features: &WasmFeatures, + ) -> Result<()> { + if !features.component_model_async() { + bail!( + offset, + "`error-context.drop` requires the component model async feature" + ) + } + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); + 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 +1886,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 +1897,8 @@ impl ComponentState { CanonicalOption::Memory(_) => "memory", CanonicalOption::Realloc(_) => "realloc", CanonicalOption::PostReturn(_) => "post-return", + CanonicalOption::Async => "async", + CanonicalOption::Callback(_) => "callback", } } @@ -1309,6 +1906,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 +1989,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 +2469,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 +3250,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::ErrorContext => Ok(ComponentDefinedType::ErrorContext), } } diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index ca42e6be15..25f7dd8400 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -891,12 +891,35 @@ impl TypeData for ComponentFuncType { } } +#[derive(Copy, Clone, Debug)] +pub(crate) enum Abi { + LowerSync, + LowerAsync, + LiftSync, + LiftAsync, + LiftAsyncStackful, +} + 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, abi: Abi) -> LoweringInfo { let mut info = LoweringInfo::default(); + let is_lower = match abi { + Abi::LowerAsync => { + 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; + } + Abi::LowerSync => true, + Abi::LiftSync | Abi::LiftAsync | Abi::LiftAsyncStackful => false, + }; + 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,28 +953,37 @@ 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); - } + match abi { + Abi::LowerAsync => unreachable!(), + Abi::LowerSync | Abi::LiftSync => { + 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; } + Abi::LiftAsync => { + info.results.push(ValType::I32); + } + Abi::LiftAsyncStackful => {} } // Memory is always required when realloc is required @@ -1027,6 +1059,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-context type. + ErrorContext, } impl TypeData for ComponentDefinedType { @@ -1034,7 +1072,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::ErrorContext => TypeInfo::new(), Self::Borrow(_) => TypeInfo::borrow(), Self::Record(r) => r.info, Self::Variant(v) => v.info, @@ -1062,7 +1106,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::ErrorContext => false, Self::Option(ty) => ty.contains_ptr(types), Self::Result { ok, err } => { ok.map(|ty| ty.contains_ptr(types)).unwrap_or(false) @@ -1091,7 +1141,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::ErrorContext => lowered_types.push(ValType::I32), Self::Option(ty) => { Self::push_variant_wasm_types([ty].into_iter(), types, lowered_types) } @@ -1159,6 +1214,9 @@ impl ComponentDefinedType { ComponentDefinedType::Result { .. } => "result", ComponentDefinedType::Own(_) => "own", ComponentDefinedType::Borrow(_) => "borrow", + ComponentDefinedType::Future(_) => "future", + ComponentDefinedType::Stream(_) => "stream", + ComponentDefinedType::ErrorContext => "error-context", } } } @@ -1873,7 +1931,8 @@ impl TypeAlloc { match &self[id] { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) - | ComponentDefinedType::Enum(_) => {} + | ComponentDefinedType::Enum(_) + | ComponentDefinedType::ErrorContext => {} ComponentDefinedType::Record(r) => { for ty in r.fields.values() { self.free_variables_valtype(ty, set); @@ -1891,7 +1950,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 +1966,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 +2071,7 @@ impl TypeAlloc { let ty = &self[id]; match ty { // Primitives are always considered named - ComponentDefinedType::Primitive(_) => true, + ComponentDefinedType::Primitive(_) | ComponentDefinedType::ErrorContext => true, // These structures are never allowed to be anonymous, so they // themselves must be named. @@ -2028,15 +2094,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 +2258,8 @@ where match &mut tmp { ComponentDefinedType::Primitive(_) | ComponentDefinedType::Flags(_) - | ComponentDefinedType::Enum(_) => {} + | ComponentDefinedType::Enum(_) + | ComponentDefinedType::ErrorContext => {} ComponentDefinedType::Record(r) => { for ty in r.fields.values_mut() { any_changed |= self.remap_valtype(ty, map); @@ -2205,7 +2277,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 +2293,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 +3225,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()), + (ErrorContext, ErrorContext) => Ok(()), + (ErrorContext, b) => bail!(offset, "expected {}, found error-context", b.desc()), } } diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index d0595ceb53..d8bce0b8a8 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -11,7 +11,7 @@ use crate::{ Export, ExternalKind, GlobalType, Import, Matches, MemoryType, PackedIndex, RecGroup, RefType, Result, SubType, TableType, TypeRef, UnpackedIndex, ValType, WithRecGroup, }; -use crate::{HeapType, ValidatorId}; +use crate::{FuncType, HeapType, ValidatorId}; use alloc::sync::Arc; use core::ops::{Deref, DerefMut, Index, Range}; use core::{hash::Hash, mem}; @@ -951,6 +951,12 @@ impl TypeList { self[group_id].start } + /// Helper for interning a function type as a rec group; see + /// [`Self::intern_sub_type`]. + pub fn intern_func_type(&mut self, ty: FuncType, offset: usize) -> CoreTypeId { + self.intern_sub_type(SubType::func(ty, false), offset) + } + /// Get the `CoreTypeId` for a local index into a rec group. pub fn rec_group_local_id( &self, diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 94bffb218f..0a40fe163f 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::ErrorContext => self.print_type_keyword("error-context")?, } Ok(()) @@ -775,11 +800,36 @@ 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(()) } + fn print_intrinsic( + &mut self, + state: &mut State, + group: &str, + body: &dyn Fn(&mut Self, &mut State) -> Result<()>, + ) -> Result<()> { + self.start_group("core func ")?; + self.print_name(&state.core.func_names, state.core.funcs)?; + self.result.write_str(" ")?; + self.start_group(group)?; + body(self, state)?; + self.end_group()?; + self.end_group()?; + debug_assert_eq!(state.core.func_to_type.len(), state.core.funcs as usize); + state.core.funcs += 1; + state.core.func_to_type.push(None); + Ok(()) + } + pub(crate) fn print_canonical_functions( &mut self, state: &mut State, @@ -814,55 +864,27 @@ impl Printer<'_, '_> { func_index, options, } => { - self.start_group("core func ")?; - self.print_name(&state.core.func_names, state.core.funcs)?; - self.result.write_str(" ")?; - self.start_group("canon lower ")?; - self.start_group("func ")?; - self.print_idx(&state.component.func_names, func_index)?; - self.end_group()?; - self.print_canonical_options(state, &options)?; - self.end_group()?; - self.end_group()?; - debug_assert!(state.core.func_to_type.len() == state.core.funcs as usize); - state.core.funcs += 1; - state.core.func_to_type.push(None) + self.print_intrinsic(state, "canon lower ", &|me, state| { + me.start_group("func ")?; + me.print_idx(&state.component.func_names, func_index)?; + me.end_group()?; + me.print_canonical_options(state, &options) + })?; } CanonicalFunction::ResourceNew { resource } => { - self.start_group("core func ")?; - self.print_name(&state.core.func_names, state.core.funcs)?; - self.result.write_str(" ")?; - self.start_group("canon resource.new ")?; - self.print_idx(&state.component.type_names, resource)?; - self.end_group()?; - self.end_group()?; - debug_assert!(state.core.func_to_type.len() == state.core.funcs as usize); - state.core.funcs += 1; - state.core.func_to_type.push(None) + self.print_intrinsic(state, "canon resource.new ", &|me, state| { + me.print_idx(&state.component.type_names, resource) + })?; } CanonicalFunction::ResourceDrop { resource } => { - self.start_group("core func ")?; - self.print_name(&state.core.func_names, state.core.funcs)?; - self.result.write_str(" ")?; - self.start_group("canon resource.drop ")?; - self.print_idx(&state.component.type_names, resource)?; - self.end_group()?; - self.end_group()?; - debug_assert!(state.core.func_to_type.len() == state.core.funcs as usize); - state.core.funcs += 1; - state.core.func_to_type.push(None) + self.print_intrinsic(state, "canon resource.drop ", &|me, state| { + me.print_idx(&state.component.type_names, resource) + })?; } CanonicalFunction::ResourceRep { resource } => { - self.start_group("core func ")?; - self.print_name(&state.core.func_names, state.core.funcs)?; - self.result.write_str(" ")?; - self.start_group("canon resource.rep ")?; - self.print_idx(&state.component.type_names, resource)?; - self.end_group()?; - self.end_group()?; - debug_assert!(state.core.func_to_type.len() == state.core.funcs as usize); - state.core.funcs += 1; - state.core.func_to_type.push(None) + self.print_intrinsic(state, "canon resource.rep ", &|me, state| { + me.print_idx(&state.component.type_names, resource) + })?; } CanonicalFunction::ThreadSpawn { func_ty_index: func_index, @@ -885,6 +907,150 @@ impl Printer<'_, '_> { self.end_group()?; state.core.funcs += 1; } + CanonicalFunction::TaskBackpressure => { + self.print_intrinsic(state, "canon task.backpressure", &|_, _| Ok(()))?; + } + CanonicalFunction::TaskReturn { type_index } => { + self.print_intrinsic(state, "canon task.return ", &|me, state| { + me.print_idx(&state.component.type_names, type_index) + })?; + } + CanonicalFunction::TaskWait { async_, memory } => { + self.print_intrinsic(state, "canon task.wait ", &|me, state| { + if async_ { + me.result.write_str("async ")?; + } + me.start_group("memory ")?; + me.print_idx(&state.core.memory_names, memory)?; + me.end_group() + })?; + } + CanonicalFunction::TaskPoll { async_, memory } => { + self.print_intrinsic(state, "canon task.poll ", &|me, state| { + if async_ { + me.result.write_str("async ")?; + } + me.start_group("memory ")?; + me.print_idx(&state.core.memory_names, memory)?; + me.end_group() + })?; + } + CanonicalFunction::TaskYield { async_ } => { + self.print_intrinsic(state, "canon task.yield", &|me, _| { + if async_ { + me.result.write_str(" async")?; + } + Ok(()) + })?; + } + CanonicalFunction::SubtaskDrop => { + self.print_intrinsic(state, "canon subtask.drop", &|_, _| Ok(()))?; + } + CanonicalFunction::StreamNew { ty } => { + self.print_intrinsic(state, "canon stream.new ", &|me, state| { + me.print_idx(&state.component.type_names, ty) + })?; + } + CanonicalFunction::StreamRead { ty, options } => { + self.print_intrinsic(state, "canon stream.read ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + me.print_canonical_options(state, &options) + })?; + } + CanonicalFunction::StreamWrite { ty, options } => { + self.print_intrinsic(state, "canon stream.write ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + me.print_canonical_options(state, &options) + })?; + } + CanonicalFunction::StreamCancelRead { ty, async_ } => { + self.print_intrinsic(state, "canon stream.cancel-read ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + if async_ { + me.result.write_str(" async")?; + } + Ok(()) + })?; + } + CanonicalFunction::StreamCancelWrite { ty, async_ } => { + self.print_intrinsic(state, "canon stream.cancel-write ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + if async_ { + me.result.write_str(" async")?; + } + Ok(()) + })?; + } + CanonicalFunction::StreamCloseReadable { ty } => { + self.print_intrinsic(state, "canon stream.close-readable ", &|me, state| { + me.print_idx(&state.component.type_names, ty) + })?; + } + CanonicalFunction::StreamCloseWritable { ty } => { + self.print_intrinsic(state, "canon stream.close-writable ", &|me, state| { + me.print_idx(&state.component.type_names, ty) + })?; + } + CanonicalFunction::FutureNew { ty } => { + self.print_intrinsic(state, "canon future.new ", &|me, state| { + me.print_idx(&state.component.type_names, ty) + })?; + } + CanonicalFunction::FutureRead { ty, options } => { + self.print_intrinsic(state, "canon future.read ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + me.print_canonical_options(state, &options) + })?; + } + CanonicalFunction::FutureWrite { ty, options } => { + self.print_intrinsic(state, "canon future.write ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + me.print_canonical_options(state, &options) + })?; + } + CanonicalFunction::FutureCancelRead { ty, async_ } => { + self.print_intrinsic(state, "canon future.cancel-read ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + if async_ { + me.result.write_str(" async")?; + } + Ok(()) + })?; + } + CanonicalFunction::FutureCancelWrite { ty, async_ } => { + self.print_intrinsic(state, "canon future.cancel-write ", &|me, state| { + me.print_idx(&state.component.type_names, ty)?; + if async_ { + me.result.write_str(" async")?; + } + Ok(()) + })?; + } + CanonicalFunction::FutureCloseReadable { ty } => { + self.print_intrinsic(state, "canon future.close-readable ", &|me, state| { + me.print_idx(&state.component.type_names, ty) + })?; + } + CanonicalFunction::FutureCloseWritable { ty } => { + self.print_intrinsic(state, "canon future.close-writable ", &|me, state| { + me.print_idx(&state.component.type_names, ty) + })?; + } + CanonicalFunction::ErrorContextNew { options } => { + self.print_intrinsic(state, "canon error-context.new", &|me, state| { + me.print_canonical_options(state, &options) + })?; + } + CanonicalFunction::ErrorContextDebugMessage { options } => { + self.print_intrinsic( + state, + "canon error-context.debug-message", + &|me, state| me.print_canonical_options(state, &options), + )?; + } + CanonicalFunction::ErrorContextDrop => { + self.print_intrinsic(state, "canon error-context.drop", &|_, _| Ok(()))?; + } } } @@ -1079,7 +1245,7 @@ impl Printer<'_, '_> { self.start_group("core func ")?; self.print_name(&state.core.func_names, state.core.funcs)?; self.end_group()?; - debug_assert!(state.core.func_to_type.len() == state.core.funcs as usize); + debug_assert_eq!(state.core.func_to_type.len(), state.core.funcs as usize); state.core.funcs += 1; state.core.func_to_type.push(None) } @@ -1105,7 +1271,7 @@ impl Printer<'_, '_> { self.start_group("core tag ")?; self.print_name(&state.core.tag_names, state.core.tags as u32)?; self.end_group()?; - debug_assert!(state.core.tag_to_type.len() == state.core.tags as usize); + debug_assert_eq!(state.core.tag_to_type.len(), state.core.tags as usize); state.core.tags += 1; state.core.tag_to_type.push(None) } diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index b38863192c..856b50d7ff 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -133,6 +133,8 @@ fn encode_defined_type(encoder: ComponentDefinedTypeEncoder, ty: &ComponentDefin } ComponentDefinedType::Own(i) => encoder.own((*i).into()), ComponentDefinedType::Borrow(i) => encoder.borrow((*i).into()), + ComponentDefinedType::Stream(s) => encoder.stream(s.element.as_ref().into()), + ComponentDefinedType::Future(f) => encoder.future(f.element.as_deref().map(Into::into)), } } @@ -355,6 +357,104 @@ impl<'a> Encoder<'a> { self.core_func_names.push(name); self.funcs.thread_hw_concurrency(); } + CanonicalFuncKind::TaskBackpressure => { + self.core_func_names.push(name); + self.funcs.task_backpressure(); + } + CanonicalFuncKind::TaskReturn(info) => { + self.core_func_names.push(name); + self.funcs.task_return(info.ty.into()); + } + CanonicalFuncKind::TaskWait(info) => { + self.core_func_names.push(name); + self.funcs.task_wait(info.async_, info.memory.idx.into()); + } + CanonicalFuncKind::TaskPoll(info) => { + self.core_func_names.push(name); + self.funcs.task_poll(info.async_, info.memory.idx.into()); + } + CanonicalFuncKind::TaskYield(info) => { + self.core_func_names.push(name); + self.funcs.task_yield(info.async_); + } + CanonicalFuncKind::SubtaskDrop => { + self.core_func_names.push(name); + self.funcs.subtask_drop(); + } + CanonicalFuncKind::StreamNew(info) => { + self.core_func_names.push(name); + self.funcs.stream_new(info.ty.into()); + } + CanonicalFuncKind::StreamRead(info) => { + self.core_func_names.push(name); + self.funcs + .stream_read(info.ty.into(), info.opts.iter().map(Into::into)); + } + CanonicalFuncKind::StreamWrite(info) => { + self.core_func_names.push(name); + self.funcs + .stream_write(info.ty.into(), info.opts.iter().map(Into::into)); + } + CanonicalFuncKind::StreamCancelRead(info) => { + self.core_func_names.push(name); + self.funcs.stream_cancel_read(info.ty.into(), info.async_); + } + CanonicalFuncKind::StreamCancelWrite(info) => { + self.core_func_names.push(name); + self.funcs.stream_cancel_write(info.ty.into(), info.async_); + } + CanonicalFuncKind::StreamCloseReadable(info) => { + self.core_func_names.push(name); + self.funcs.stream_close_readable(info.ty.into()); + } + CanonicalFuncKind::StreamCloseWritable(info) => { + self.core_func_names.push(name); + self.funcs.stream_close_writable(info.ty.into()); + } + CanonicalFuncKind::FutureNew(info) => { + self.core_func_names.push(name); + self.funcs.future_new(info.ty.into()); + } + CanonicalFuncKind::FutureRead(info) => { + self.core_func_names.push(name); + self.funcs + .future_read(info.ty.into(), info.opts.iter().map(Into::into)); + } + CanonicalFuncKind::FutureWrite(info) => { + self.core_func_names.push(name); + self.funcs + .future_write(info.ty.into(), info.opts.iter().map(Into::into)); + } + CanonicalFuncKind::FutureCancelRead(info) => { + self.core_func_names.push(name); + self.funcs.future_cancel_read(info.ty.into(), info.async_); + } + CanonicalFuncKind::FutureCancelWrite(info) => { + self.core_func_names.push(name); + self.funcs.future_cancel_write(info.ty.into(), info.async_); + } + CanonicalFuncKind::FutureCloseReadable(info) => { + self.core_func_names.push(name); + self.funcs.future_close_readable(info.ty.into()); + } + CanonicalFuncKind::FutureCloseWritable(info) => { + self.core_func_names.push(name); + self.funcs.future_close_writable(info.ty.into()); + } + CanonicalFuncKind::ErrorContextNew(info) => { + self.core_func_names.push(name); + self.funcs + .error_context_new(info.opts.iter().map(Into::into)); + } + CanonicalFuncKind::ErrorContextDebugMessage(info) => { + self.core_func_names.push(name); + self.funcs + .error_context_debug_message(info.opts.iter().map(Into::into)); + } + CanonicalFuncKind::ErrorContextDrop => { + self.core_func_names.push(name); + self.funcs.error_context_drop(); + } } self.flush(Some(self.funcs.id())); @@ -822,6 +922,8 @@ impl From<&CanonOpt<'_>> for wasm_encoder::CanonicalOption { CanonOpt::Memory(m) => Self::Memory(m.idx.into()), CanonOpt::Realloc(f) => Self::Realloc(f.idx.into()), CanonOpt::PostReturn(f) => Self::PostReturn(f.idx.into()), + CanonOpt::Async => Self::Async, + CanonOpt::Callback(f) => Self::Callback(f.idx.into()), } } } diff --git a/crates/wast/src/component/expand.rs b/crates/wast/src/component/expand.rs index 11ada7bd45..2df745f0c9 100644 --- a/crates/wast/src/component/expand.rs +++ b/crates/wast/src/component/expand.rs @@ -116,7 +116,10 @@ impl<'a> Expander<'a> { self.expand_canonical_func(f); None } - ComponentField::CoreFunc(f) => self.expand_core_func(f), + ComponentField::CoreFunc(f) => Some(self.expand_core_func(CoreFunc { + kind: mem::replace(&mut f.kind, CoreFuncKind::ErrorContextDrop), + ..*f + })), ComponentField::Func(f) => self.expand_func(f), ComponentField::Import(i) => { self.expand_item_sig(&mut i.item); @@ -266,13 +269,36 @@ impl<'a> Expander<'a> { | CanonicalFuncKind::ResourceRep(_) | CanonicalFuncKind::ResourceDrop(_) | CanonicalFuncKind::ThreadSpawn(_) - | CanonicalFuncKind::ThreadHwConcurrency(_) => {} + | CanonicalFuncKind::ThreadHwConcurrency(_) + | CanonicalFuncKind::TaskBackpressure + | CanonicalFuncKind::TaskReturn(_) + | CanonicalFuncKind::TaskWait(_) + | CanonicalFuncKind::TaskPoll(_) + | CanonicalFuncKind::TaskYield(_) + | CanonicalFuncKind::SubtaskDrop + | CanonicalFuncKind::StreamNew(_) + | CanonicalFuncKind::StreamRead(_) + | CanonicalFuncKind::StreamWrite(_) + | CanonicalFuncKind::StreamCancelRead(_) + | CanonicalFuncKind::StreamCancelWrite(_) + | CanonicalFuncKind::StreamCloseReadable(_) + | CanonicalFuncKind::StreamCloseWritable(_) + | CanonicalFuncKind::FutureNew(_) + | CanonicalFuncKind::FutureRead(_) + | CanonicalFuncKind::FutureWrite(_) + | CanonicalFuncKind::FutureCancelRead(_) + | CanonicalFuncKind::FutureCancelWrite(_) + | CanonicalFuncKind::FutureCloseReadable(_) + | CanonicalFuncKind::FutureCloseWritable(_) + | CanonicalFuncKind::ErrorContextNew(_) + | CanonicalFuncKind::ErrorContextDebugMessage(_) + | CanonicalFuncKind::ErrorContextDrop => {} } } - fn expand_core_func(&mut self, func: &mut CoreFunc<'a>) -> Option> { - match &mut func.kind { - CoreFuncKind::Alias(a) => Some(ComponentField::Alias(Alias { + fn expand_core_func(&mut self, func: CoreFunc<'a>) -> ComponentField<'a> { + match func.kind { + CoreFuncKind::Alias(a) => ComponentField::Alias(Alias { span: func.span, id: func.id, name: func.name, @@ -281,47 +307,193 @@ impl<'a> Expander<'a> { name: a.name, kind: core::ExportKind::Func, }, - })), - CoreFuncKind::Lower(info) => Some(ComponentField::CanonicalFunc(CanonicalFunc { + }), + CoreFuncKind::Lower(info) => ComponentField::CanonicalFunc(CanonicalFunc { span: func.span, id: func.id, name: func.name, - kind: CanonicalFuncKind::Lower(mem::take(info)), - })), - CoreFuncKind::ResourceNew(info) => Some(ComponentField::CanonicalFunc(CanonicalFunc { + kind: CanonicalFuncKind::Lower(info), + }), + CoreFuncKind::ResourceNew(info) => ComponentField::CanonicalFunc(CanonicalFunc { span: func.span, id: func.id, name: func.name, - kind: CanonicalFuncKind::ResourceNew(mem::take(info)), - })), - CoreFuncKind::ResourceDrop(info) => { - Some(ComponentField::CanonicalFunc(CanonicalFunc { + kind: CanonicalFuncKind::ResourceNew(info), + }), + CoreFuncKind::ResourceDrop(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::ResourceDrop(info), + }), + CoreFuncKind::ResourceRep(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::ResourceRep(info), + }), + CoreFuncKind::ThreadSpawn(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::ThreadSpawn(info), + }), + CoreFuncKind::ThreadHwConcurrency(info) => { + ComponentField::CanonicalFunc(CanonicalFunc { span: func.span, id: func.id, name: func.name, - kind: CanonicalFuncKind::ResourceDrop(mem::take(info)), - })) + kind: CanonicalFuncKind::ThreadHwConcurrency(info), + }) } - CoreFuncKind::ResourceRep(info) => Some(ComponentField::CanonicalFunc(CanonicalFunc { + CoreFuncKind::TaskBackpressure => ComponentField::CanonicalFunc(CanonicalFunc { span: func.span, id: func.id, name: func.name, - kind: CanonicalFuncKind::ResourceRep(mem::take(info)), - })), - CoreFuncKind::ThreadSpawn(info) => Some(ComponentField::CanonicalFunc(CanonicalFunc { + kind: CanonicalFuncKind::TaskBackpressure, + }), + CoreFuncKind::TaskReturn(info) => ComponentField::CanonicalFunc(CanonicalFunc { span: func.span, id: func.id, name: func.name, - kind: CanonicalFuncKind::ThreadSpawn(mem::take(info)), - })), - CoreFuncKind::ThreadHwConcurrency(info) => { - Some(ComponentField::CanonicalFunc(CanonicalFunc { + kind: CanonicalFuncKind::TaskReturn(info), + }), + CoreFuncKind::TaskWait(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::TaskWait(info), + }), + CoreFuncKind::TaskPoll(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::TaskPoll(info), + }), + CoreFuncKind::TaskYield(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::TaskYield(info), + }), + CoreFuncKind::SubtaskDrop => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::SubtaskDrop, + }), + CoreFuncKind::StreamNew(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::StreamNew(info), + }), + CoreFuncKind::StreamRead(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::StreamRead(info), + }), + CoreFuncKind::StreamWrite(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::StreamWrite(info), + }), + CoreFuncKind::StreamCancelRead(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::StreamCancelRead(info), + }), + CoreFuncKind::StreamCancelWrite(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::StreamCancelWrite(info), + }), + CoreFuncKind::StreamCloseReadable(info) => { + ComponentField::CanonicalFunc(CanonicalFunc { span: func.span, id: func.id, name: func.name, - kind: CanonicalFuncKind::ThreadHwConcurrency(mem::take(info)), - })) + kind: CanonicalFuncKind::StreamCloseReadable(info), + }) + } + CoreFuncKind::StreamCloseWritable(info) => { + ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::StreamCloseWritable(info), + }) } + CoreFuncKind::FutureNew(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureNew(info), + }), + CoreFuncKind::FutureRead(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureRead(info), + }), + CoreFuncKind::FutureWrite(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureWrite(info), + }), + CoreFuncKind::FutureCancelRead(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureCancelRead(info), + }), + CoreFuncKind::FutureCancelWrite(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureCancelWrite(info), + }), + CoreFuncKind::FutureCloseReadable(info) => { + ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureCloseReadable(info), + }) + } + CoreFuncKind::FutureCloseWritable(info) => { + ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::FutureCloseWritable(info), + }) + } + CoreFuncKind::ErrorContextNew(info) => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::ErrorContextNew(info), + }), + CoreFuncKind::ErrorContextDebugMessage(info) => { + ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::ErrorContextDebugMessage(info), + }) + } + CoreFuncKind::ErrorContextDrop => ComponentField::CanonicalFunc(CanonicalFunc { + span: func.span, + id: func.id, + name: func.name, + kind: CanonicalFuncKind::ErrorContextDrop, + }), } } @@ -589,6 +761,14 @@ impl<'a> Expander<'a> { } } ComponentDefinedType::Own(_) | ComponentDefinedType::Borrow(_) => {} + ComponentDefinedType::Stream(t) => { + self.expand_component_val_ty(&mut t.element); + } + ComponentDefinedType::Future(t) => { + if let Some(ty) = &mut t.element { + self.expand_component_val_ty(ty); + } + } } } diff --git a/crates/wast/src/component/func.rs b/crates/wast/src/component/func.rs index ec4d9fc4c3..49a42eda87 100644 --- a/crates/wast/src/component/func.rs +++ b/crates/wast/src/component/func.rs @@ -53,6 +53,29 @@ pub enum CoreFuncKind<'a> { ResourceRep(CanonResourceRep<'a>), ThreadSpawn(CanonThreadSpawn<'a>), ThreadHwConcurrency(CanonThreadHwConcurrency), + TaskBackpressure, + TaskReturn(CanonTaskReturn<'a>), + TaskWait(CanonTaskWait<'a>), + TaskPoll(CanonTaskPoll<'a>), + TaskYield(CanonTaskYield), + SubtaskDrop, + StreamNew(CanonStreamNew<'a>), + StreamRead(CanonStreamRead<'a>), + StreamWrite(CanonStreamWrite<'a>), + StreamCancelRead(CanonStreamCancelRead<'a>), + StreamCancelWrite(CanonStreamCancelWrite<'a>), + StreamCloseReadable(CanonStreamCloseReadable<'a>), + StreamCloseWritable(CanonStreamCloseWritable<'a>), + FutureNew(CanonFutureNew<'a>), + FutureRead(CanonFutureRead<'a>), + FutureWrite(CanonFutureWrite<'a>), + FutureCancelRead(CanonFutureCancelRead<'a>), + FutureCancelWrite(CanonFutureCancelWrite<'a>), + FutureCloseReadable(CanonFutureCloseReadable<'a>), + FutureCloseWritable(CanonFutureCloseWritable<'a>), + ErrorContextNew(CanonErrorContextNew<'a>), + ErrorContextDebugMessage(CanonErrorContextDebugMessage<'a>), + ErrorContextDrop, } impl<'a> Parse<'a> for CoreFuncKind<'a> { @@ -79,6 +102,55 @@ impl<'a> Parse<'a> for CoreFuncKind<'a> { Ok(CoreFuncKind::ThreadSpawn(parser.parse()?)) } else if l.peek::()? { Ok(CoreFuncKind::ThreadHwConcurrency(parser.parse()?)) + } else if l.peek::()? { + parser.parse::()?; + Ok(CoreFuncKind::TaskBackpressure) + } else if l.peek::()? { + Ok(CoreFuncKind::TaskReturn(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::TaskWait(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::TaskPoll(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::TaskYield(parser.parse()?)) + } else if l.peek::()? { + parser.parse::()?; + Ok(CoreFuncKind::SubtaskDrop) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamNew(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamRead(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamWrite(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamCancelRead(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamCancelWrite(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamCloseReadable(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::StreamCloseWritable(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureNew(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureRead(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureWrite(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureCancelRead(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureCancelWrite(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureCloseReadable(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::FutureCloseWritable(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::ErrorContextNew(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::ErrorContextDebugMessage(parser.parse()?)) + } else if l.peek::()? { + parser.parse::()?; + Ok(CoreFuncKind::ErrorContextDrop) } else { Err(l.error()) } @@ -270,6 +342,30 @@ pub enum CanonicalFuncKind<'a> { ThreadSpawn(CanonThreadSpawn<'a>), ThreadHwConcurrency(CanonThreadHwConcurrency), + + TaskBackpressure, + TaskReturn(CanonTaskReturn<'a>), + TaskWait(CanonTaskWait<'a>), + TaskPoll(CanonTaskPoll<'a>), + TaskYield(CanonTaskYield), + SubtaskDrop, + StreamNew(CanonStreamNew<'a>), + StreamRead(CanonStreamRead<'a>), + StreamWrite(CanonStreamWrite<'a>), + StreamCancelRead(CanonStreamCancelRead<'a>), + StreamCancelWrite(CanonStreamCancelWrite<'a>), + StreamCloseReadable(CanonStreamCloseReadable<'a>), + StreamCloseWritable(CanonStreamCloseWritable<'a>), + FutureNew(CanonFutureNew<'a>), + FutureRead(CanonFutureRead<'a>), + FutureWrite(CanonFutureWrite<'a>), + FutureCancelRead(CanonFutureCancelRead<'a>), + FutureCancelWrite(CanonFutureCancelWrite<'a>), + FutureCloseReadable(CanonFutureCloseReadable<'a>), + FutureCloseWritable(CanonFutureCloseWritable<'a>), + ErrorContextNew(CanonErrorContextNew<'a>), + ErrorContextDebugMessage(CanonErrorContextDebugMessage<'a>), + ErrorContextDrop, } /// Information relating to lifting a core function. @@ -360,14 +456,6 @@ impl<'a> Parse<'a> for CanonResourceNew<'a> { } } -impl Default for CanonResourceNew<'_> { - fn default() -> Self { - CanonResourceNew { - ty: Index::Num(0, Span::from_offset(0)), - } - } -} - /// Information relating to the `resource.drop` intrinsic. #[derive(Debug)] pub struct CanonResourceDrop<'a> { @@ -385,14 +473,6 @@ impl<'a> Parse<'a> for CanonResourceDrop<'a> { } } -impl Default for CanonResourceDrop<'_> { - fn default() -> Self { - CanonResourceDrop { - ty: Index::Num(0, Span::from_offset(0)), - } - } -} - /// Information relating to the `resource.rep` intrinsic. #[derive(Debug)] pub struct CanonResourceRep<'a> { @@ -410,14 +490,6 @@ impl<'a> Parse<'a> for CanonResourceRep<'a> { } } -impl Default for CanonResourceRep<'_> { - fn default() -> Self { - CanonResourceRep { - ty: Index::Num(0, Span::from_offset(0)), - } - } -} - /// Information relating to the `thread.spawn` intrinsic. #[derive(Debug)] pub struct CanonThreadSpawn<'a> { @@ -435,14 +507,6 @@ impl<'a> Parse<'a> for CanonThreadSpawn<'a> { } } -impl Default for CanonThreadSpawn<'_> { - fn default() -> Self { - CanonThreadSpawn { - ty: Index::Num(0, Span::from_offset(0)), - } - } -} - /// Information relating to the `thread.spawn` intrinsic. #[derive(Debug)] pub struct CanonThreadHwConcurrency; @@ -454,9 +518,383 @@ impl<'a> Parse<'a> for CanonThreadHwConcurrency { } } -impl Default for CanonThreadHwConcurrency { - fn default() -> Self { - Self +/// Information relating to the `task.return` intrinsic. +#[derive(Debug)] +pub struct CanonTaskReturn<'a> { + /// The core function type representing the signature of this intrinsic. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonTaskReturn<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `task.wait` intrinsic. +#[derive(Debug)] +pub struct CanonTaskWait<'a> { + /// If true, the component instance may be reentered during a call to this + /// intrinsic. + pub async_: bool, + /// The memory to use when returning an event to the caller. + pub memory: CoreItemRef<'a, kw::memory>, +} + +impl<'a> Parse<'a> for CanonTaskWait<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let async_ = parser.parse::>()?.is_some(); + let memory = parser.parens(|parser| { + let span = parser.parse::()?.0; + parse_trailing_item_ref(kw::memory(span), parser) + })?; + + Ok(Self { async_, memory }) + } +} + +/// Information relating to the `task.poll` intrinsic. +#[derive(Debug)] +pub struct CanonTaskPoll<'a> { + /// If true, the component instance may be reentered during a call to this + /// intrinsic. + pub async_: bool, + /// The memory to use when returning an event to the caller. + pub memory: CoreItemRef<'a, kw::memory>, +} + +impl<'a> Parse<'a> for CanonTaskPoll<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let async_ = parser.parse::>()?.is_some(); + let memory = parser.parens(|parser| { + let span = parser.parse::()?.0; + parse_trailing_item_ref(kw::memory(span), parser) + })?; + + Ok(Self { async_, memory }) + } +} + +/// Information relating to the `task.yield` intrinsic. +#[derive(Debug)] +pub struct CanonTaskYield { + /// If true, the component instance may be reentered during a call to this + /// intrinsic. + pub async_: bool, +} + +impl<'a> Parse<'a> for CanonTaskYield { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let async_ = parser.parse::>()?.is_some(); + + Ok(Self { async_ }) + } +} + +/// Information relating to the `stream.new` intrinsic. +#[derive(Debug)] +pub struct CanonStreamNew<'a> { + /// The stream type to instantiate. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonStreamNew<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `stream.read` intrinsic. +#[derive(Debug)] +pub struct CanonStreamRead<'a> { + /// The stream type to instantiate. + pub ty: Index<'a>, + /// The canonical options for storing values. + pub opts: Vec>, +} + +impl<'a> Parse<'a> for CanonStreamRead<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + opts: parser.parse()?, + }) + } +} + +/// Information relating to the `stream.write` intrinsic. +#[derive(Debug)] +pub struct CanonStreamWrite<'a> { + /// The stream type to instantiate. + pub ty: Index<'a>, + /// The canonical options for loading values. + pub opts: Vec>, +} + +impl<'a> Parse<'a> for CanonStreamWrite<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + opts: parser.parse()?, + }) + } +} + +/// Information relating to the `stream.cancel-read` intrinsic. +#[derive(Debug)] +pub struct CanonStreamCancelRead<'a> { + /// The stream type to instantiate. + pub ty: Index<'a>, + /// If false, block until cancel is finished; otherwise return BLOCKED if + /// necessary. + pub async_: bool, +} + +impl<'a> Parse<'a> for CanonStreamCancelRead<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + async_: parser.parse::>()?.is_some(), + }) + } +} + +/// Information relating to the `stream.cancel-write` intrinsic. +#[derive(Debug)] +pub struct CanonStreamCancelWrite<'a> { + /// The stream type to instantiate. + pub ty: Index<'a>, + /// If false, block until cancel is finished; otherwise return BLOCKED if + /// necessary. + pub async_: bool, +} + +impl<'a> Parse<'a> for CanonStreamCancelWrite<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + async_: parser.parse::>()?.is_some(), + }) + } +} + +/// Information relating to the `stream.close-readable` intrinsic. +#[derive(Debug)] +pub struct CanonStreamCloseReadable<'a> { + /// The stream type to close. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonStreamCloseReadable<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `stream.close-writable` intrinsic. +#[derive(Debug)] +pub struct CanonStreamCloseWritable<'a> { + /// The stream type to close. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonStreamCloseWritable<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `future.new` intrinsic. +#[derive(Debug)] +pub struct CanonFutureNew<'a> { + /// The future type to instantiate. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonFutureNew<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `future.read` intrinsic. +#[derive(Debug)] +pub struct CanonFutureRead<'a> { + /// The future type to instantiate. + pub ty: Index<'a>, + /// The canonical options for storing values. + pub opts: Vec>, +} + +impl<'a> Parse<'a> for CanonFutureRead<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + opts: parser.parse()?, + }) + } +} + +/// Information relating to the `future.write` intrinsic. +#[derive(Debug)] +pub struct CanonFutureWrite<'a> { + /// The future type to instantiate. + pub ty: Index<'a>, + /// The canonical options for loading values. + pub opts: Vec>, +} + +impl<'a> Parse<'a> for CanonFutureWrite<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + opts: parser.parse()?, + }) + } +} + +/// Information relating to the `future.cancel-read` intrinsic. +#[derive(Debug)] +pub struct CanonFutureCancelRead<'a> { + /// The future type to instantiate. + pub ty: Index<'a>, + /// If false, block until cancel is finished; otherwise return BLOCKED if + /// necessary. + pub async_: bool, +} + +impl<'a> Parse<'a> for CanonFutureCancelRead<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + async_: parser.parse::>()?.is_some(), + }) + } +} + +/// Information relating to the `future.cancel-write` intrinsic. +#[derive(Debug)] +pub struct CanonFutureCancelWrite<'a> { + /// The future type to instantiate. + pub ty: Index<'a>, + /// If false, block until cancel is finished; otherwise return BLOCKED if + /// necessary. + pub async_: bool, +} + +impl<'a> Parse<'a> for CanonFutureCancelWrite<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + async_: parser.parse::>()?.is_some(), + }) + } +} + +/// Information relating to the `future.close-readable` intrinsic. +#[derive(Debug)] +pub struct CanonFutureCloseReadable<'a> { + /// The future type to close. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonFutureCloseReadable<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `future.close-writable` intrinsic. +#[derive(Debug)] +pub struct CanonFutureCloseWritable<'a> { + /// The future type to close. + pub ty: Index<'a>, +} + +impl<'a> Parse<'a> for CanonFutureCloseWritable<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + ty: parser.parse()?, + }) + } +} + +/// Information relating to the `error-context.new` intrinsic. +#[derive(Debug)] +pub struct CanonErrorContextNew<'a> { + /// The canonical options for loading the debug message. + pub opts: Vec>, +} + +impl<'a> Parse<'a> for CanonErrorContextNew<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + opts: parser.parse()?, + }) + } +} + +/// Information relating to the `error-context.debug-message` intrinsic. +#[derive(Debug)] +pub struct CanonErrorContextDebugMessage<'a> { + /// The canonical options for storing the debug message. + pub opts: Vec>, +} + +impl<'a> Parse<'a> for CanonErrorContextDebugMessage<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + Ok(Self { + opts: parser.parse()?, + }) } } @@ -475,6 +913,10 @@ pub enum CanonOpt<'a> { Realloc(CoreItemRef<'a, kw::func>), /// Call the specified function after the lifted function has returned. PostReturn(CoreItemRef<'a, kw::func>), + /// Use the async ABI for lifting or lowering. + Async, + /// Use the specified function to deliver async events to stackless coroutines. + Callback(CoreItemRef<'a, kw::func>), } impl<'a> Parse<'a> for CanonOpt<'a> { @@ -489,6 +931,9 @@ impl<'a> Parse<'a> for CanonOpt<'a> { } else if l.peek::()? { parser.parse::()?; Ok(Self::StringLatin1Utf16) + } else if l.peek::()? { + parser.parse::()?; + Ok(Self::Async) } else if l.peek::()? { parser.parens(|parser| { let mut l = parser.lookahead1(); @@ -508,6 +953,11 @@ impl<'a> Parse<'a> for CanonOpt<'a> { Ok(CanonOpt::PostReturn( parser.parse::>()?.0, )) + } else if l.peek::()? { + parser.parse::()?; + Ok(CanonOpt::Callback( + parser.parse::>()?.0, + )) } else { Err(l.error()) } diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs index 48eaaf6a65..c488b58175 100644 --- a/crates/wast/src/component/resolve.rs +++ b/crates/wast/src/component/resolve.rs @@ -386,7 +386,72 @@ impl<'a> Resolver<'a> { CanonicalFuncKind::ThreadSpawn(info) => { self.resolve_ns(&mut info.ty, Ns::CoreType)?; } - CanonicalFuncKind::ThreadHwConcurrency(_) => {} + CanonicalFuncKind::ThreadHwConcurrency(_) + | CanonicalFuncKind::TaskBackpressure + | CanonicalFuncKind::TaskYield(_) + | CanonicalFuncKind::SubtaskDrop + | CanonicalFuncKind::ErrorContextDrop => {} + CanonicalFuncKind::TaskReturn(info) => { + self.resolve_ns(&mut info.ty, Ns::CoreType)?; + } + CanonicalFuncKind::TaskWait(info) => { + self.core_item_ref(&mut info.memory)?; + } + CanonicalFuncKind::TaskPoll(info) => { + self.core_item_ref(&mut info.memory)?; + } + CanonicalFuncKind::StreamNew(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::StreamRead(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + self.canon_opts(&mut info.opts)?; + } + CanonicalFuncKind::StreamWrite(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + self.canon_opts(&mut info.opts)?; + } + CanonicalFuncKind::StreamCancelRead(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::StreamCancelWrite(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::StreamCloseReadable(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::StreamCloseWritable(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::FutureNew(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::FutureRead(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + self.canon_opts(&mut info.opts)?; + } + CanonicalFuncKind::FutureWrite(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + self.canon_opts(&mut info.opts)?; + } + CanonicalFuncKind::FutureCancelRead(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::FutureCancelWrite(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::FutureCloseReadable(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::FutureCloseWritable(info) => { + self.resolve_ns(&mut info.ty, Ns::Type)?; + } + CanonicalFuncKind::ErrorContextNew(info) => { + self.canon_opts(&mut info.opts)?; + } + CanonicalFuncKind::ErrorContextDebugMessage(info) => { + self.canon_opts(&mut info.opts)?; + } } Ok(()) @@ -395,9 +460,14 @@ impl<'a> Resolver<'a> { fn canon_opts(&mut self, opts: &mut [CanonOpt<'a>]) -> Result<(), Error> { for opt in opts { match opt { - CanonOpt::StringUtf8 | CanonOpt::StringUtf16 | CanonOpt::StringLatin1Utf16 => {} + CanonOpt::StringUtf8 + | CanonOpt::StringUtf16 + | CanonOpt::StringLatin1Utf16 + | CanonOpt::Async => {} CanonOpt::Memory(r) => self.core_item_ref(r)?, - CanonOpt::Realloc(r) | CanonOpt::PostReturn(r) => self.core_item_ref(r)?, + CanonOpt::Realloc(r) | CanonOpt::PostReturn(r) | CanonOpt::Callback(r) => { + self.core_item_ref(r)? + } } } @@ -482,6 +552,14 @@ impl<'a> Resolver<'a> { ComponentDefinedType::Own(t) | ComponentDefinedType::Borrow(t) => { self.resolve_ns(t, Ns::Type)?; } + ComponentDefinedType::Stream(s) => { + self.component_val_type(&mut s.element)?; + } + ComponentDefinedType::Future(f) => { + if let Some(ty) = &mut f.element { + self.component_val_type(ty)?; + } + } } Ok(()) } @@ -882,7 +960,30 @@ impl<'a> ComponentState<'a> { | CanonicalFuncKind::ResourceRep(_) | CanonicalFuncKind::ResourceDrop(_) | CanonicalFuncKind::ThreadSpawn(_) - | CanonicalFuncKind::ThreadHwConcurrency(_) => { + | CanonicalFuncKind::ThreadHwConcurrency(_) + | CanonicalFuncKind::TaskBackpressure + | CanonicalFuncKind::TaskReturn(_) + | CanonicalFuncKind::TaskWait(_) + | CanonicalFuncKind::TaskPoll(_) + | CanonicalFuncKind::TaskYield(_) + | CanonicalFuncKind::SubtaskDrop + | CanonicalFuncKind::StreamNew(_) + | CanonicalFuncKind::StreamRead(_) + | CanonicalFuncKind::StreamWrite(_) + | CanonicalFuncKind::StreamCancelRead(_) + | CanonicalFuncKind::StreamCancelWrite(_) + | CanonicalFuncKind::StreamCloseReadable(_) + | CanonicalFuncKind::StreamCloseWritable(_) + | CanonicalFuncKind::FutureNew(_) + | CanonicalFuncKind::FutureRead(_) + | CanonicalFuncKind::FutureWrite(_) + | CanonicalFuncKind::FutureCancelRead(_) + | CanonicalFuncKind::FutureCancelWrite(_) + | CanonicalFuncKind::FutureCloseReadable(_) + | CanonicalFuncKind::FutureCloseWritable(_) + | CanonicalFuncKind::ErrorContextNew(_) + | CanonicalFuncKind::ErrorContextDebugMessage(_) + | CanonicalFuncKind::ErrorContextDrop => { self.core_funcs.register(f.id, "core func")? } }, diff --git a/crates/wast/src/component/types.rs b/crates/wast/src/component/types.rs index 1345b408b4..abefa1d10d 100644 --- a/crates/wast/src/component/types.rs +++ b/crates/wast/src/component/types.rs @@ -388,6 +388,8 @@ pub enum ComponentDefinedType<'a> { Result(ResultType<'a>), Own(Index<'a>), Borrow(Index<'a>), + Stream(Stream<'a>), + Future(Future<'a>), } impl<'a> ComponentDefinedType<'a> { @@ -415,6 +417,10 @@ impl<'a> ComponentDefinedType<'a> { } else if l.peek::()? { parser.parse::()?; Ok(Self::Borrow(parser.parse()?)) + } else if l.peek::()? { + Ok(Self::Stream(parser.parse()?)) + } else if l.peek::()? { + Ok(Self::Future(parser.parse()?)) } else { Err(l.error()) } @@ -684,6 +690,38 @@ impl<'a> Parse<'a> for ResultType<'a> { } } +/// A stream type. +#[derive(Debug)] +pub struct Stream<'a> { + /// The element type of the stream. + pub element: Box>, +} + +impl<'a> Parse<'a> for Stream<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + Ok(Self { + element: Box::new(parser.parse()?), + }) + } +} + +/// A future type. +#[derive(Debug)] +pub struct Future<'a> { + /// The element type of the future, if any. + pub element: Option>>, +} + +impl<'a> Parse<'a> for Future<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + Ok(Self { + element: parser.parse::>()?.map(Box::new), + }) + } +} + /// A component function type with parameters and result. #[derive(Debug)] pub struct ComponentFunctionType<'a> { diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index b197ed33b1..109316f612 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -556,8 +556,35 @@ pub mod kw { custom_keyword!(thread); custom_keyword!(thread_spawn = "thread.spawn"); custom_keyword!(thread_hw_concurrency = "thread.hw_concurrency"); + custom_keyword!(task_backpressure = "task.backpressure"); + custom_keyword!(task_return = "task.return"); + custom_keyword!(task_wait = "task.wait"); + custom_keyword!(task_poll = "task.poll"); + custom_keyword!(task_yield = "task.yield"); + custom_keyword!(subtask_drop = "subtask.drop"); + custom_keyword!(stream_new = "stream.new"); + custom_keyword!(stream_read = "stream.read"); + custom_keyword!(stream_write = "stream.write"); + custom_keyword!(stream_cancel_read = "stream.cancel-read"); + custom_keyword!(stream_cancel_write = "stream.cancel-write"); + custom_keyword!(stream_close_readable = "stream.close-readable"); + custom_keyword!(stream_close_writable = "stream.close-writable"); + custom_keyword!(future_new = "future.new"); + custom_keyword!(future_read = "future.read"); + custom_keyword!(future_write = "future.write"); + custom_keyword!(future_cancel_read = "future.cancel-read"); + custom_keyword!(future_cancel_write = "future.cancel-write"); + custom_keyword!(future_close_readable = "future.close-readable"); + custom_keyword!(future_close_writable = "future.close-writable"); + custom_keyword!(error_context_new = "error-context.new"); + custom_keyword!(error_context_debug_message = "error-context.debug-message"); + custom_keyword!(error_context_drop = "error-context.drop"); custom_keyword!(wait); custom_keyword!(definition); + custom_keyword!(r#async = "async"); + custom_keyword!(callback); + custom_keyword!(stream); + custom_keyword!(future); } /// Common annotations used to parse WebAssembly text files. diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml index 0155909957..f6b9e78710 100644 --- a/crates/wit-component/Cargo.toml +++ b/crates/wit-component/Cargo.toml @@ -35,6 +35,7 @@ serde_derive = { workspace = true } serde_json = { workspace = true } [dev-dependencies] +wasmparser = { workspace = true, features = ['component-model', 'features'] } wasmprinter = { workspace = true, features = ['component-model'] } glob = "0.3.0" pretty_assertions = "1.3.0" diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 5fb025b269..14a12b9e82 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 let AbiVariant::GuestExportAsync | AbiVariant::GuestExportAsyncStackful = abi { + 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::ErrorContext => Self::empty(), TypeDefKind::Unknown => unreachable!(), }, Type::String => Self::STRING, @@ -481,7 +493,13 @@ impl<'a> EncodingState<'a> { // Next encode all required functions from this imported interface // into the instance type. for (_, func) in interface.functions.iter() { - if !info.lowerings.contains_key(&func.name) { + if !(info + .lowerings + .contains_key(&(func.name.clone(), AbiVariant::GuestImport)) + || info + .lowerings + .contains_key(&(func.name.clone(), AbiVariant::GuestImportAsync))) + { continue; } log::trace!("encoding function type for `{}`", func.name); @@ -514,7 +532,13 @@ impl<'a> EncodingState<'a> { WorldItem::Interface { .. } | WorldItem::Type(_) => continue, }; let name = resolve.name_world_key(name); - if !info.lowerings.contains_key(&name) { + if !(info + .lowerings + .contains_key(&(name.clone(), AbiVariant::GuestImport)) + || info + .lowerings + .contains_key(&(name.clone(), AbiVariant::GuestImportAsync))) + { continue; } log::trace!("encoding function type for `{}`", func.name); @@ -615,6 +639,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 +648,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 +1027,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 +1053,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 +1206,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)); @@ -1186,7 +1226,7 @@ impl<'a> EncodingState<'a> { encoding, } => { let interface = &self.info.import_map[interface]; - let (name, _) = interface.lowerings.get_index(*index).unwrap(); + let ((name, _), _) = interface.lowerings.get_index(*index).unwrap(); let func_index = match &interface.interface { Some(interface_id) => { let instance_index = self.imported_instances[interface_id]; @@ -1230,6 +1270,150 @@ 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, info.imported)?; + + 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::ErrorContextNew { encoding } + | ShimKind::ErrorContextDebugMessage { 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 { + for_module, + 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)? + .collect::>(), + ) + } + _ => unreachable!(), + }, }; exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); @@ -1243,6 +1427,45 @@ impl<'a> EncodingState<'a> { Ok(()) } + /// Encode the specified `stream` or `future` type in the component using + /// either the `root_import_type_encoder` or the `root_export_type_encoder` + /// depending on the value of `imported`. + /// + /// Note that the payload type `T` of `stream` or `future` may be an + /// imported or exported type, and that determines the appropriate type + /// encoder to use. + fn payload_type_index(&mut self, ty: TypeId, imported: bool) -> Result { + // `stream` and `future` types don't have owners, but their payload + // types (or the payload type of the payload type, etc. in the case of + // nesting) might have an owner, in which case we need to find that in + // order to make the types match up e.g. when we're exporting a resource + // that's used as a payload type. + fn owner(resolve: &Resolve, ty: TypeId) -> Option { + let def = &resolve.types[ty]; + match &def.kind { + TypeDefKind::Future(Some(Type::Id(ty))) => owner(resolve, *ty), + TypeDefKind::Stream(Type::Id(ty)) => owner(resolve, *ty), + _ => match &def.owner { + TypeOwner::World(_) | TypeOwner::None => None, + TypeOwner::Interface(id) => Some(*id), + }, + } + } + + let resolve = &self.info.encoder.metadata.resolve; + let ComponentValType::Type(type_index) = if imported { + self.root_import_type_encoder(None) + } else { + let owner = owner(resolve, ty); + self.root_export_type_encoder(owner) + } + .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 +1606,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,14 +1695,204 @@ 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, info.imported)?; + 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, + imported, + async_, + } => { + let ty = self.payload_type_index(*ty, *imported)?; + let index = self.component.stream_cancel_read(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::StreamCancelWrite { + ty, + imported, + async_, + } => { + let ty = self.payload_type_index(*ty, *imported)?; + let index = self.component.stream_cancel_write(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::StreamCloseReadable { ty, imported } => { + let type_index = self.payload_type_index(*ty, *imported)?; + let index = self.component.stream_close_readable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::StreamCloseWritable { ty, imported } => { + let type_index = self.payload_type_index(*ty, *imported)?; + 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, info.imported)?; + 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, + imported, + async_, + } => { + let ty = self.payload_type_index(*ty, *imported)?; + let index = self.component.future_cancel_read(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::FutureCancelWrite { + ty, + imported, + async_, + } => { + let ty = self.payload_type_index(*ty, *imported)?; + let index = self.component.future_cancel_write(ty, *async_); + return Ok((ExportKind::Func, index)); + } + Import::FutureCloseReadable { ty, imported } => { + let type_index = self.payload_type_index(*ty, *imported)?; + let index = self.component.future_close_readable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::FutureCloseWritable { ty, imported } => { + let type_index = self.payload_type_index(*ty, *imported)?; + let index = self.component.future_close_writable(type_index); + return Ok((ExportKind::Func, index)); + } + Import::ErrorContextNew { encoding } => { + let index = self.component.core_alias_export( + self.shim_instance_index + .expect("shim should be instantiated"), + &shims.shims[&ShimKind::ErrorContextNew { + encoding: *encoding, + }] + .name, + ExportKind::Func, + ); + return Ok((ExportKind::Func, index)); + } + Import::ErrorContextDebugMessage { encoding, realloc } => { + let index = self.component.core_alias_export( + self.shim_instance_index + .expect("shim should be instantiated"), + &shims.shims[&ShimKind::ErrorContextDebugMessage { + for_module, + encoding: *encoding, + realloc, + }] + .name, + ExportKind::Func, + ); + return Ok((ExportKind::Func, index)); + } + Import::ErrorContextDrop => { + let index = self.component.error_context_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]; - let (index, _, lowering) = import.lowerings.get_full(name).unwrap(); + let (index, _, lowering) = import.lowerings.get_full(&(name.clone(), abi)).unwrap(); let metadata = self.info.module_metadata_for(for_module); let index = match lowering { @@ -1481,7 +1907,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 +2069,16 @@ struct Shim<'a> { sig: WasmSignature, } +/// Which variation of `{stream|future}.{read|write}` we're emitting for a +/// `ShimKind::PayloadFunc`. +#[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 +2110,48 @@ enum ShimKind<'a> { /// The exported function name of this destructor in the core module. export: &'a str, }, + /// A shim used for a `{stream|future}.{read|write}` built-in function, + /// which must refer to the core module instance's memory from/to which + /// payload values must be lifted/lowered. + PayloadFunc { + /// Which instance to pull the `realloc` function and string encoding + /// from, if necessary. + for_module: CustomModule<'a>, + /// Whether this read/write call is using the `async` option. + async_: bool, + /// Additional information regarding the function where this `stream` or + /// `future` type appeared, which we use in combination with + /// `for_module` to determine which `realloc` and string encoding to + /// use, as well as which type to specify when emitting the built-in. + info: &'a PayloadInfo, + /// Which variation of `{stream|future}.{read|write}` we're emitting. + kind: PayloadFuncKind, + }, + /// A shim used for the `task.wait` built-in function, which must refer to + /// the core module instance's memory to which results will be written. + TaskWait { async_: bool }, + /// A shim used for the `task.poll` built-in function, which must refer to + /// the core module instance's memory to which results will be written. + TaskPoll { async_: bool }, + /// A shim used for the `error-context.new` built-in function, which must + /// refer to the core module instance's memory from which the debug message + /// will be read. + ErrorContextNew { + /// String encoding to use when lifting the debug message. + encoding: StringEncoding, + }, + /// A shim used for the `error-context.debug-message` built-in function, + /// which must refer to the core module instance's memory to which results + /// will be written. + ErrorContextDebugMessage { + /// Which instance to pull the `realloc` function from, if necessary. + for_module: CustomModule<'a>, + /// The string encoding to use when lowering the debug message. + encoding: StringEncoding, + /// The realloc function to use when allocating linear memory for the + /// debug message. + realloc: &'a str, + }, } /// Indicator for which module is being used for a lowering or where options @@ -1703,8 +2188,31 @@ 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 { + let (key, name, interface_key, abi) = match import { // These imports don't require shims, they can be satisfied // as-needed when required. Import::ImportedResourceDrop(..) @@ -1713,7 +2221,145 @@ impl<'a> Shims<'a> { | Import::Item(_) | Import::ExportedResourceDrop(..) | Import::ExportedResourceRep(..) - | Import::ExportedResourceNew(..) => continue, + | Import::ExportedResourceNew(..) + | Import::ErrorContextDrop + | 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::ErrorContextNew { encoding } => { + let name = self.shims.len().to_string(); + self.push(Shim { + name, + debug_name: "error-new".to_string(), + options: RequiredOptions::empty(), + kind: ShimKind::ErrorContextNew { + encoding: *encoding, + }, + sig: WasmSignature { + params: vec![WasmType::I32; 2], + results: vec![WasmType::I32], + indirect_params: false, + retptr: false, + }, + }); + continue; + } + + 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::ErrorContextDebugMessage { + for_module, + encoding: *encoding, + realloc, + }, + 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,13 +2400,13 @@ 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) => { - (key, name, Some(resolve.name_world_key(key))) + Import::InterfaceFunc(key, _, name, abi) => { + (key, name, Some(resolve.name_world_key(key)), *abi) } - Import::WorldFunc(key, name) => (key, name, None), + Import::WorldFunc(key, name, abi) => (key, name, None, *abi), }; let interface = &world.import_map[&interface_key]; - let (index, _, lowering) = interface.lowerings.get_full(name).unwrap(); + let (index, _, lowering) = interface.lowerings.get_full(&(name.clone(), abi)).unwrap(); let shim_name = self.shims.len().to_string(); match lowering { Lowering::Direct | Lowering::ResourceDrop(_) => {} diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 6a47c26cf8..1ed436640c 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::ErrorContext => self.encode_error_context()?, 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_context(&mut self) -> Result { + let (index, encoder) = self.defined_type(); + encoder.error_context(); + 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..edbc2afa7a 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -47,7 +47,7 @@ pub struct ComponentWorld<'a> { #[derive(Debug)] pub struct ImportedInterface { - pub lowerings: IndexMap, + pub lowerings: IndexMap<(String, AbiVariant), Lowering>, pub interface: Option, } @@ -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,27 +414,34 @@ 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()) => {} - _ => return, + let mut abis = Vec::with_capacity(2); + if let Some(set) = required.interface_funcs.get(&self.interface) { + if set.contains(&(func.name.as_str(), AbiVariant::GuestImport)) { + abis.push(AbiVariant::GuestImport); + } + if set.contains(&(func.name.as_str(), AbiVariant::GuestImportAsync)) { + abis.push(AbiVariant::GuestImportAsync); + } } - log::trace!("add func {}", func.name); - let options = RequiredOptions::for_import(resolve, func); - let lowering = if options.is_empty() { - Lowering::Direct - } else { - let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - Lowering::Indirect { sig, options } - }; + for abi in abis { + log::trace!("add func {} {abi:?}", func.name); + let options = RequiredOptions::for_import(resolve, func, abi); + let lowering = if options.is_empty() { + Lowering::Direct + } else { + let sig = resolve.wasm_signature(abi, func); + Lowering::Indirect { sig, options } + }; - let prev = self.lowerings.insert(func.name.clone(), lowering); - assert!(prev.is_none()); + let prev = self.lowerings.insert((func.name.clone(), abi), lowering); + assert!(prev.is_none()); + } } fn add_type(&mut self, required: &Required<'_>, resolve: &Resolve, id: TypeId) { @@ -447,7 +454,9 @@ impl ImportedInterface { if required.resource_drops.contains(&id) { let name = format!("{name}_drop"); - let prev = self.lowerings.insert(name, Lowering::ResourceDrop(id)); + let prev = self + .lowerings + .insert((name, AbiVariant::GuestImport), Lowering::ResourceDrop(id)); assert!(prev.is_none()); } } diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 13e03ce0c6..5845c2849e 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -534,12 +534,21 @@ impl WitPrinter { self.output.push_str(">"); } TypeDefKind::Type(ty) => self.print_type_name(resolve, ty)?, - TypeDefKind::Future(_) => { - todo!("document has an unnamed future type") + TypeDefKind::Future(ty) => { + if let Some(ty) = ty { + self.output.push_str("future<"); + self.print_type_name(resolve, ty)?; + self.output.push_str(">"); + } else { + self.output.push_str("future"); + } } - TypeDefKind::Stream(_) => { - todo!("document has an unnamed stream type") + TypeDefKind::Stream(ty) => { + self.output.push_str("stream<"); + self.print_type_name(resolve, ty)?; + self.output.push_str(">"); } + TypeDefKind::ErrorContext => self.output.push_str("error-context"), TypeDefKind::Unknown => unreachable!(), } } @@ -695,8 +704,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::ErrorContext => panic!("no need to declare error-contexts"), TypeDefKind::Unknown => unreachable!(), } } @@ -987,6 +997,7 @@ fn is_keyword(name: &str) -> bool { | "with" | "include" | "constructor" + | "error-context" ) } diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index 21598710f1..010640f3ab 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -1,7 +1,8 @@ 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}; 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,57 @@ pub enum ImportInstance { Names(IndexMap), } +/// Represents metadata about a `stream` or `future` type for a specific +/// payload type `T`. +/// +/// Currently, the name mangling scheme we use to represent `stream` and +/// `future` intrinsics as core module function imports refers to a specific +/// `stream` or `future` type by naming an imported or exported component +/// function which has that type as a parameter or return type (where the +/// specific type is refered to using an ordinal numbering scheme). Not only +/// does this approach unambiguously indicate the type of interest, but it +/// allows us to reuse the `realloc`, string encoding, memory, etc. used by that +/// function when emitting intrinsic declarations. +/// +/// TODO: Rather than reusing the same canon opts as the function in which the +/// type appears, consider encoding them in the name mangling stream on an +/// individual basis, similar to how we encode `error-context.*` built-in +/// imports. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct PayloadInfo { + /// The original, mangled import name used to import this built-in + /// (currently used only for hashing and debugging). + pub name: String, + /// The resolved type id for the `stream` or `future` type of interest. + pub ty: TypeId, + /// The component-level function import or export where the type appeared as + /// a parameter or result type. + pub function: Function, + /// The world key representing the import or export context of `function`. + pub key: WorldKey, + /// The interface that `function` was imported from or exported in, if any. + pub interface: Option, + /// Whether `function` is being imported or exported. + /// + /// This may affect how we emit the declaration of the built-in, e.g. if the + /// payload type is an exported resource. + pub imported: bool, +} + +impl Hash for PayloadInfo { + /// We derive `Hash` for this type by hand and exclude the `function` field + /// because (A) `Function` doesn't implement `Hash` and (B) the other fields + /// are sufficient to uniquely identify the type of interest, which function + /// it appeared in, and which parameter or return type we found it in. + 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 +176,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. /// @@ -180,13 +234,169 @@ pub enum Import { /// (should probably subsume `MainModule*` and maybe `AdapterExport` above /// one day. Item(Item), + + /// A `canon task.return` intrinsic for an exported function. + /// + /// This allows an exported function to return a value and then continue + /// running. + /// + /// As of this writing, only async-lifted exports use `task.return`, but the + /// plan is to also support it for sync-lifted exports in the future as + /// well. + ExportedTaskReturn(Function), + + /// A `canon task.backpressure` intrinsic. + /// + /// This allows the guest to dynamically indicate whether it's ready for + /// additional concurrent calls. + TaskBackpressure, + + /// A `canon task.wait` intrinsic. + /// + /// This allows the guest to wait for any pending calls to async-lowered + /// imports and/or `stream` and `future` operations to complete without + /// unwinding the current Wasm stack. + TaskWait { async_: bool }, + + /// A `canon task.poll` intrinsic. + /// + /// This allows the guest to check whether any pending calls to + /// async-lowered imports and/or `stream` and `future` operations have + /// completed without unwinding the current Wasm stack and without blocking. + TaskPoll { async_: bool }, + + /// A `canon task.wait` intrinsic. + /// + /// This allows the guest to yield (e.g. during an computationally-intensive + /// operation) and allow other subtasks to make progress. + TaskYield { async_: bool }, + + /// A `canon subtask.drop` intrinsic. + /// + /// This allows the guest to release its handle to an completed subtask. + SubtaskDrop, + + /// A `canon stream.new` intrinsic. + /// + /// This allows the guest to create a new `stream` of the specified type. + StreamNew(PayloadInfo), + + /// A `canon stream.read` intrinsic. + /// + /// This allows the guest to read the next values (if any) from the specifed + /// stream. + StreamRead { async_: bool, info: PayloadInfo }, + + /// A `canon stream.write` intrinsic. + /// + /// This allows the guest to write one or more values to the specifed + /// stream. + StreamWrite { async_: bool, info: PayloadInfo }, + + /// A `canon stream.cancel-read` intrinsic. + /// + /// This allows the guest to cancel a pending read it initiated earlier (but + /// which may have already partially or entirely completed). + StreamCancelRead { + ty: TypeId, + imported: bool, + async_: bool, + }, + + /// A `canon stream.cancel-write` intrinsic. + /// + /// This allows the guest to cancel a pending write it initiated earlier + /// (but which may have already partially or entirely completed). + StreamCancelWrite { + ty: TypeId, + imported: bool, + async_: bool, + }, + + /// A `canon stream.close-readable` intrinsic. + /// + /// This allows the guest to close the readable end of a `stream`. + StreamCloseReadable { ty: TypeId, imported: bool }, + + /// A `canon stream.close-writable` intrinsic. + /// + /// This allows the guest to close the writable end of a `stream`. + StreamCloseWritable { ty: TypeId, imported: bool }, + + /// A `canon future.new` intrinsic. + /// + /// This allows the guest to create a new `future` of the specified type. + FutureNew(PayloadInfo), + + /// A `canon future.read` intrinsic. + /// + /// This allows the guest to read the value (if any) from the specifed + /// future. + FutureRead { async_: bool, info: PayloadInfo }, + + /// A `canon future.write` intrinsic. + /// + /// This allows the guest to write a value to the specifed future. + FutureWrite { async_: bool, info: PayloadInfo }, + + /// A `canon future.cancel-read` intrinsic. + /// + /// This allows the guest to cancel a pending read it initiated earlier (but + /// which may have already completed). + FutureCancelRead { + ty: TypeId, + imported: bool, + async_: bool, + }, + + /// A `canon future.cancel-write` intrinsic. + /// + /// This allows the guest to cancel a pending write it initiated earlier + /// (but which may have already completed). + FutureCancelWrite { + ty: TypeId, + imported: bool, + async_: bool, + }, + + /// A `canon future.close-readable` intrinsic. + /// + /// This allows the guest to close the readable end of a `future`. + FutureCloseReadable { ty: TypeId, imported: bool }, + + /// A `canon future.close-writable` intrinsic. + /// + /// This allows the guest to close the writable end of a `future`. + FutureCloseWritable { ty: TypeId, imported: bool }, + + /// A `canon error-context.new` intrinsic. + /// + /// This allows the guest to create a new `error-context` instance with a + /// specified debug message. + ErrorContextNew { encoding: StringEncoding }, + + /// A `canon error-context.debug-message` intrinsic. + /// + /// This allows the guest to retrieve the debug message from a + /// `error-context` instance. Note that the content of this message might + /// not be identical to what was passed in to `error-context.new`. + ErrorContextDebugMessage { + encoding: StringEncoding, + realloc: String, + }, + + /// A `canon error-context.drop` intrinsic. + /// + /// This allows the guest to release its handle to the specified + /// `error-context` instance. + ErrorContextDrop, } 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 +404,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 +538,97 @@ 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_for_export = |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, false)?; + // 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_context_drop() { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ErrorContextDrop); + } + + 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 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::ErrorContextNew { encoding }); + } + + 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::ErrorContextDebugMessage { + encoding, + realloc: realloc.to_owned(), + }); + } + + if let Some(import) = async_import_for_export(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 +652,14 @@ impl ImportMap { }; if let Some(interface) = interface.strip_prefix(names.import_exported_intrinsic_prefix()) { + if let Some(import) = async_import_for_export(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 +691,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 +793,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 +824,10 @@ pub enum Export { /// `cabi_realloc_adapter` ReallocForAdapter, + + WorldFuncCallback(WorldKey), + + InterfaceFuncCallback(WorldKey, String), } impl ExportMap { @@ -569,6 +877,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 +922,32 @@ 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 if let Some(name) = names.async_stackful_name(name) { + (AbiVariant::GuestExportAsyncStackful, 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 +973,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 +1008,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> { @@ -759,7 +1115,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 +1127,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 +1184,26 @@ 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 async_stackful_name<'a>(&self, s: &'a str) -> Option<&'a 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, + name: &str, + resolve: &Resolve, + world: &World, + ty: &FuncType, + ) -> Result>; fn module_to_interface( &self, module: &str, @@ -885,6 +1261,59 @@ 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 async_stackful_name<'a>(&self, s: &'a str) -> Option<&'a str> { + _ = s; + None + } + fn error_context_new(&self, s: &str) -> Option { + _ = s; + None + } + fn error_context_debug_message<'a>(&self, s: &'a str) -> Option<(StringEncoding, &'a str)> { + _ = s; + None + } + fn error_context_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 +1449,277 @@ 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 async_stackful_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[async-stackful]") + } + fn error_context_new(&self, s: &str) -> Option { + parse_encoding( + s.strip_prefix("[error-context-new;encoding=")? + .strip_suffix("]")?, + ) + } + 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_context_drop(&self) -> Option<&str> { + Some("[error-context-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(|| module.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, + )?; + let info = info(key)?; + Import::FutureCancelWrite { + async_, + ty: info.ty, + imported: info.imported, + } + } else if let Some(key) = match_payload_prefix(name, "[future-cancel-read-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32], [ValType::I32]), + ty, + )?; + let info = info(key)?; + Import::FutureCancelRead { + async_, + ty: info.ty, + imported: info.imported, + } + } 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)?; + let info = info(key)?; + Import::FutureCloseWritable { + ty: info.ty, + imported: info.imported, + } + } 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)?; + let info = info(key)?; + Import::FutureCloseReadable { + ty: info.ty, + imported: info.imported, + } + } 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, + )?; + let info = info(key)?; + Import::StreamCancelWrite { + async_, + ty: info.ty, + imported: info.imported, + } + } else if let Some(key) = match_payload_prefix(name, "[stream-cancel-read-") { + validate_func_sig( + name, + &FuncType::new([ValType::I32], [ValType::I32]), + ty, + )?; + let info = info(key)?; + Import::StreamCancelRead { + async_, + ty: info.ty, + imported: info.imported, + } + } 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)?; + let info = info(key)?; + Import::StreamCloseWritable { + ty: info.ty, + imported: info.imported, + } + } 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)?; + let info = info(key)?; + Import::StreamCloseReadable { + ty: info.ty, + imported: info.imported, + } + } else { + bail!("unrecognized payload import: {name}"); + }, + ) + } else { + None + }, + ) + } fn module_to_interface( &self, module: &str, @@ -1304,3 +2004,68 @@ 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}` found"); + }; + 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/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 4a335bbbff..ffb06476df 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -4,6 +4,7 @@ use pretty_assertions::assert_eq; use std::{borrow::Cow, fs, path::Path}; use wasm_encoder::{Encode, Section}; use wasm_metadata::{Metadata, Payload}; +use wasmparser::{Parser, Validator, WasmFeatures}; use wit_component::{ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter}; use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup}; @@ -90,7 +91,7 @@ fn run_test(path: &Path) -> Result<()> { .with_context(|| format!("failed to read core module at {module_path:?}"))?; adapters .try_fold( - ComponentEncoder::default().module(&module)?.validate(true), + ComponentEncoder::default().module(&module)?, |encoder, path| { let (name, wasm) = read_name_and_module("adapt-", &path?, &resolve, pkg_id)?; Ok::<_, Error>(encoder.adapter(&name, &wasm)?) @@ -109,7 +110,7 @@ fn run_test(path: &Path) -> Result<()> { // Sort list to ensure deterministic order, which determines priority in cases of duplicate symbols: libs.sort_by(|(_, a, _), (_, b, _)| a.cmp(b)); - let mut linker = Linker::default().validate(true); + let mut linker = Linker::default().validate(false); if path.join("stub-missing-functions").is_file() { linker = linker.stub_missing_functions(true); @@ -153,9 +154,17 @@ fn run_test(path: &Path) -> Result<()> { } }; + Validator::new_with_features(WasmFeatures::all()) + .validate_all(&bytes) + .context("failed to validate component output")?; + let wat = wasmprinter::print_bytes(&bytes).context("failed to print bytes")?; assert_output(&wat, &component_path)?; - let (pkg, resolve) = match wit_component::decode(&bytes).context("failed to decode resolve")? { + let mut parser = Parser::new(0); + parser.set_features(WasmFeatures::all()); + let (pkg, resolve) = match wit_component::decode_reader(bytes.as_slice()) + .context("failed to decode resolve")? + { DecodedWasm::WitPackage(..) => unreachable!(), DecodedWasm::Component(resolve, world) => (resolve.worlds[world].package.unwrap(), resolve), }; diff --git a/crates/wit-component/tests/components/async-builtins/component.wat b/crates/wit-component/tests/components/async-builtins/component.wat new file mode 100644 index 0000000000..443c98f09e --- /dev/null +++ b/crates/wit-component/tests/components/async-builtins/component.wat @@ -0,0 +1,159 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func (param i32) (result i32))) + (type (;3;) (func)) + (type (;4;) (func (param i32 i32) (result i32))) + (type (;5;) (func (param i32 i32 i32 i32) (result i32))) + (import "$root" "[task-backpressure]" (func (;0;) (type 0))) + (import "$root" "[task-return]foo" (func (;1;) (type 1))) + (import "[export]foo:foo/bar" "[task-return]foo" (func (;2;) (type 1))) + (import "$root" "[task-wait]" (func (;3;) (type 2))) + (import "$root" "[task-poll]" (func (;4;) (type 2))) + (import "$root" "[task-yield]" (func (;5;) (type 3))) + (import "$root" "[subtask-drop]" (func (;6;) (type 0))) + (import "$root" "[error-context-new;encoding=utf8]" (func (;7;) (type 4))) + (import "$root" "[error-context-debug-message;encoding=utf8;realloc=cabi_realloc]" (func (;8;) (type 1))) + (import "$root" "[error-context-drop]" (func (;9;) (type 0))) + (memory (;0;) 1) + (export "[async-stackful]foo" (func 10)) + (export "[async-stackful]foo:foo/bar#foo" (func 11)) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 12)) + (func (;10;) (type 1) (param i32 i32) + unreachable + ) + (func (;11;) (type 1) (param i32 i32) + unreachable + ) + (func (;12;) (type 5) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core module (;1;) + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32))) + (table (;0;) 4 4 funcref) + (export "0" (func $task-wait)) + (export "1" (func $task-poll)) + (export "2" (func $error-new)) + (export "3" (func $error-debug-message)) + (export "$imports" (table 0)) + (func $task-wait (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const 0 + call_indirect (type 0) + ) + (func $task-poll (;1;) (type 0) (param i32) (result i32) + local.get 0 + i32.const 1 + call_indirect (type 0) + ) + (func $error-new (;2;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 2 + call_indirect (type 1) + ) + (func $error-debug-message (;3;) (type 2) (param i32 i32) + local.get 0 + local.get 1 + i32.const 3 + call_indirect (type 2) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;2;) + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 0))) + (import "" "2" (func (;2;) (type 1))) + (import "" "3" (func (;3;) (type 2))) + (import "" "$imports" (table (;0;) 4 4 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2 3) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 1)) + (core func (;0;) (canon task.backpressure)) + (core type (;0;) (func (param i32 i32))) + (core func (;1;) (canon task.return 0)) + (alias core export 0 "0" (core func (;2;))) + (alias core export 0 "1" (core func (;3;))) + (core func (;4;) (canon task.yield)) + (core func (;5;) (canon subtask.drop)) + (alias core export 0 "2" (core func (;6;))) + (alias core export 0 "3" (core func (;7;))) + (core func (;8;) (canon error-context.drop)) + (core instance (;1;) + (export "[task-backpressure]" (func 0)) + (export "[task-return]foo" (func 1)) + (export "[task-wait]" (func 2)) + (export "[task-poll]" (func 3)) + (export "[task-yield]" (func 4)) + (export "[subtask-drop]" (func 5)) + (export "[error-context-new;encoding=utf8]" (func 6)) + (export "[error-context-debug-message;encoding=utf8;realloc=cabi_realloc]" (func 7)) + (export "[error-context-drop]" (func 8)) + ) + (core type (;1;) (func (param i32 i32))) + (core func (;9;) (canon task.return 1)) + (core instance (;2;) + (export "[task-return]foo" (func 9)) + ) + (core instance (;3;) (instantiate 0 + (with "$root" (instance 1)) + (with "[export]foo:foo/bar" (instance 2)) + ) + ) + (alias core export 3 "memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (core func (;10;) (canon task.wait (memory 0))) + (core func (;11;) (canon task.poll (memory 0))) + (core func (;12;) (canon error-context.new (memory 0) string-encoding=utf8)) + (alias core export 3 "cabi_realloc" (core func (;13;))) + (core func (;14;) (canon error-context.debug-message (memory 0) (realloc 13) string-encoding=utf8)) + (core instance (;4;) + (export "$imports" (table 0)) + (export "0" (func 10)) + (export "1" (func 11)) + (export "2" (func 12)) + (export "3" (func 14)) + ) + (core instance (;5;) (instantiate 2 + (with "" (instance 4)) + ) + ) + (type (;0;) (func (param "s" string) (result string))) + (alias core export 3 "[async-stackful]foo" (core func (;15;))) + (func (;0;) (type 0) (canon lift (core func 15) (memory 0) (realloc 13) string-encoding=utf8 async)) + (export (;1;) "foo" (func 0)) + (type (;1;) (func (param "s" string) (result string))) + (alias core export 3 "[async-stackful]foo:foo/bar#foo" (core func (;16;))) + (func (;2;) (type 1) (canon lift (core func 16) (memory 0) (realloc 13) string-encoding=utf8 async)) + (component (;0;) + (type (;0;) (func (param "s" string) (result string))) + (import "import-func-foo" (func (;0;) (type 0))) + (type (;1;) (func (param "s" string) (result string))) + (export (;1;) "foo" (func 0) (func (type 1))) + ) + (instance (;0;) (instantiate 0 + (with "import-func-foo" (func 2)) + ) + ) + (export (;1;) "foo:foo/bar" (instance 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/async-builtins/component.wit.print b/crates/wit-component/tests/components/async-builtins/component.wit.print new file mode 100644 index 0000000000..8c6e69aa82 --- /dev/null +++ b/crates/wit-component/tests/components/async-builtins/component.wit.print @@ -0,0 +1,6 @@ +package root:component; + +world root { + export foo: func(s: string) -> string; + export foo:foo/bar; +} diff --git a/crates/wit-component/tests/components/async-builtins/module.wat b/crates/wit-component/tests/components/async-builtins/module.wat new file mode 100644 index 0000000000..c9c90af177 --- /dev/null +++ b/crates/wit-component/tests/components/async-builtins/module.wat @@ -0,0 +1,16 @@ +(module + (import "$root" "[task-backpressure]" (func (param i32))) + (import "$root" "[task-return]foo" (func (param i32 i32))) + (import "[export]foo:foo/bar" "[task-return]foo" (func (param i32 i32))) + (import "$root" "[task-wait]" (func (param i32) (result i32))) + (import "$root" "[task-poll]" (func (param i32) (result i32))) + (import "$root" "[task-yield]" (func)) + (import "$root" "[subtask-drop]" (func (param i32))) + (import "$root" "[error-context-new;encoding=utf8]" (func (param i32 i32) (result i32))) + (import "$root" "[error-context-debug-message;encoding=utf8;realloc=cabi_realloc]" (func (param i32 i32))) + (import "$root" "[error-context-drop]" (func (param i32))) + (func (export "[async-stackful]foo") (param i32 i32) unreachable) + (func (export "[async-stackful]foo:foo/bar#foo") (param i32 i32) unreachable) + (memory (export "memory") 1) + (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) diff --git a/crates/wit-component/tests/components/async-builtins/module.wit b/crates/wit-component/tests/components/async-builtins/module.wit new file mode 100644 index 0000000000..f0629ae1d2 --- /dev/null +++ b/crates/wit-component/tests/components/async-builtins/module.wit @@ -0,0 +1,10 @@ +package foo:foo; + +interface bar { + foo: func(s: string) -> string; +} + +world module { + export bar; + export foo: func(s: string) -> string; +} diff --git a/crates/wit-component/tests/components/async-export-with-callback/component.wat b/crates/wit-component/tests/components/async-export-with-callback/component.wat new file mode 100644 index 0000000000..a8a5d6069e --- /dev/null +++ b/crates/wit-component/tests/components/async-export-with-callback/component.wat @@ -0,0 +1,58 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32 i32) (result i32))) + (memory (;0;) 1) + (export "[async]foo" (func 0)) + (export "[callback][async]foo" (func 1)) + (export "[async]foo:foo/bar#foo" (func 2)) + (export "[callback][async]foo:foo/bar#foo" (func 3)) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 4)) + (func (;0;) (type 0) (param i32 i32) (result i32) + unreachable + ) + (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;2;) (type 0) (param i32 i32) (result i32) + unreachable + ) + (func (;3;) (type 1) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;4;) (type 1) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;0;) (func (param "s" string) (result string))) + (alias core export 0 "[async]foo" (core func (;0;))) + (alias core export 0 "cabi_realloc" (core func (;1;))) + (alias core export 0 "[callback][async]foo" (core func (;2;))) + (func (;0;) (type 0) (canon lift (core func 0) (memory 0) (realloc 1) string-encoding=utf8 async (callback 2))) + (export (;1;) "foo" (func 0)) + (type (;1;) (func (param "s" string) (result string))) + (alias core export 0 "[async]foo:foo/bar#foo" (core func (;3;))) + (alias core export 0 "[callback][async]foo:foo/bar#foo" (core func (;4;))) + (func (;2;) (type 1) (canon lift (core func 3) (memory 0) (realloc 1) string-encoding=utf8 async (callback 4))) + (component (;0;) + (type (;0;) (func (param "s" string) (result string))) + (import "import-func-foo" (func (;0;) (type 0))) + (type (;1;) (func (param "s" string) (result string))) + (export (;1;) "foo" (func 0) (func (type 1))) + ) + (instance (;0;) (instantiate 0 + (with "import-func-foo" (func 2)) + ) + ) + (export (;1;) "foo:foo/bar" (instance 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/async-export-with-callback/component.wit.print b/crates/wit-component/tests/components/async-export-with-callback/component.wit.print new file mode 100644 index 0000000000..8c6e69aa82 --- /dev/null +++ b/crates/wit-component/tests/components/async-export-with-callback/component.wit.print @@ -0,0 +1,6 @@ +package root:component; + +world root { + export foo: func(s: string) -> string; + export foo:foo/bar; +} diff --git a/crates/wit-component/tests/components/async-export-with-callback/module.wat b/crates/wit-component/tests/components/async-export-with-callback/module.wat new file mode 100644 index 0000000000..3148204766 --- /dev/null +++ b/crates/wit-component/tests/components/async-export-with-callback/module.wat @@ -0,0 +1,8 @@ +(module + (func (export "[async]foo") (param i32 i32) (result i32) unreachable) + (func (export "[callback][async]foo") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "[async]foo:foo/bar#foo") (param i32 i32) (result i32) unreachable) + (func (export "[callback][async]foo:foo/bar#foo") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) diff --git a/crates/wit-component/tests/components/async-export-with-callback/module.wit b/crates/wit-component/tests/components/async-export-with-callback/module.wit new file mode 100644 index 0000000000..f0629ae1d2 --- /dev/null +++ b/crates/wit-component/tests/components/async-export-with-callback/module.wit @@ -0,0 +1,10 @@ +package foo:foo; + +interface bar { + foo: func(s: string) -> string; +} + +world module { + export bar; + export foo: func(s: string) -> string; +} diff --git a/crates/wit-component/tests/components/async-export/component.wat b/crates/wit-component/tests/components/async-export/component.wat new file mode 100644 index 0000000000..9608ae5cf8 --- /dev/null +++ b/crates/wit-component/tests/components/async-export/component.wat @@ -0,0 +1,48 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (param i32 i32 i32 i32) (result i32))) + (memory (;0;) 1) + (export "[async-stackful]foo" (func 0)) + (export "[async-stackful]foo:foo/bar#foo" (func 1)) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 2)) + (func (;0;) (type 0) (param i32 i32) + unreachable + ) + (func (;1;) (type 0) (param i32 i32) + unreachable + ) + (func (;2;) (type 1) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;0;) (func (param "s" string) (result string))) + (alias core export 0 "[async-stackful]foo" (core func (;0;))) + (alias core export 0 "cabi_realloc" (core func (;1;))) + (func (;0;) (type 0) (canon lift (core func 0) (memory 0) (realloc 1) string-encoding=utf8 async)) + (export (;1;) "foo" (func 0)) + (type (;1;) (func (param "s" string) (result string))) + (alias core export 0 "[async-stackful]foo:foo/bar#foo" (core func (;2;))) + (func (;2;) (type 1) (canon lift (core func 2) (memory 0) (realloc 1) string-encoding=utf8 async)) + (component (;0;) + (type (;0;) (func (param "s" string) (result string))) + (import "import-func-foo" (func (;0;) (type 0))) + (type (;1;) (func (param "s" string) (result string))) + (export (;1;) "foo" (func 0) (func (type 1))) + ) + (instance (;0;) (instantiate 0 + (with "import-func-foo" (func 2)) + ) + ) + (export (;1;) "foo:foo/bar" (instance 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/async-export/component.wit.print b/crates/wit-component/tests/components/async-export/component.wit.print new file mode 100644 index 0000000000..8c6e69aa82 --- /dev/null +++ b/crates/wit-component/tests/components/async-export/component.wit.print @@ -0,0 +1,6 @@ +package root:component; + +world root { + export foo: func(s: string) -> string; + export foo:foo/bar; +} diff --git a/crates/wit-component/tests/components/async-export/module.wat b/crates/wit-component/tests/components/async-export/module.wat new file mode 100644 index 0000000000..72eb941238 --- /dev/null +++ b/crates/wit-component/tests/components/async-export/module.wat @@ -0,0 +1,6 @@ +(module + (func (export "[async-stackful]foo") (param i32 i32) unreachable) + (func (export "[async-stackful]foo:foo/bar#foo") (param i32 i32) unreachable) + (memory (export "memory") 1) + (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) diff --git a/crates/wit-component/tests/components/async-export/module.wit b/crates/wit-component/tests/components/async-export/module.wit new file mode 100644 index 0000000000..f0629ae1d2 --- /dev/null +++ b/crates/wit-component/tests/components/async-export/module.wit @@ -0,0 +1,10 @@ +package foo:foo; + +interface bar { + foo: func(s: string) -> string; +} + +world module { + export bar; + export foo: func(s: string) -> string; +} diff --git a/crates/wit-component/tests/components/async-import/component.wat b/crates/wit-component/tests/components/async-import/component.wat new file mode 100644 index 0000000000..fa6360357b --- /dev/null +++ b/crates/wit-component/tests/components/async-import/component.wat @@ -0,0 +1,123 @@ +(component + (type (;0;) + (instance + (type (;0;) (func (param "s" string) (result string))) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "foo:foo/bar" (instance (;0;) (type 0))) + (type (;1;) (func (param "s" string) (result string))) + (import "foo" (func (;0;) (type 1))) + (core module (;0;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (import "$root" "[async]foo" (func (;0;) (type 0))) + (import "foo:foo/bar" "[async]foo" (func (;1;) (type 0))) + (import "$root" "foo" (func (;2;) (type 1))) + (import "foo:foo/bar" "foo" (func (;3;) (type 1))) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 4)) + (func (;4;) (type 2) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core module (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32))) + (table (;0;) 4 4 funcref) + (export "0" (func $"indirect-$root-[async]foo")) + (export "1" (func $indirect-$root-foo)) + (export "2" (func $"indirect-foo:foo/bar-[async]foo")) + (export "3" (func $indirect-foo:foo/bar-foo)) + (export "$imports" (table 0)) + (func $"indirect-$root-[async]foo" (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 0 + call_indirect (type 0) + ) + (func $indirect-$root-foo (;1;) (type 1) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 1 + call_indirect (type 1) + ) + (func $"indirect-foo:foo/bar-[async]foo" (;2;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 2 + call_indirect (type 0) + ) + (func $indirect-foo:foo/bar-foo (;3;) (type 1) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 3 + call_indirect (type 1) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;2;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 1))) + (import "" "2" (func (;2;) (type 0))) + (import "" "3" (func (;3;) (type 1))) + (import "" "$imports" (table (;0;) 4 4 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2 3) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 1)) + (alias core export 0 "0" (core func (;0;))) + (alias core export 0 "1" (core func (;1;))) + (core instance (;1;) + (export "[async]foo" (func 0)) + (export "foo" (func 1)) + ) + (alias core export 0 "2" (core func (;2;))) + (alias core export 0 "3" (core func (;3;))) + (core instance (;2;) + (export "[async]foo" (func 2)) + (export "foo" (func 3)) + ) + (core instance (;3;) (instantiate 0 + (with "$root" (instance 1)) + (with "foo:foo/bar" (instance 2)) + ) + ) + (alias core export 3 "memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (alias core export 3 "cabi_realloc" (core func (;4;))) + (core func (;5;) (canon lower (func 0) (memory 0) (realloc 4) string-encoding=utf8 async)) + (core func (;6;) (canon lower (func 0) (memory 0) (realloc 4) string-encoding=utf8)) + (alias export 0 "foo" (func (;1;))) + (core func (;7;) (canon lower (func 1) (memory 0) (realloc 4) string-encoding=utf8 async)) + (alias export 0 "foo" (func (;2;))) + (core func (;8;) (canon lower (func 2) (memory 0) (realloc 4) string-encoding=utf8)) + (core instance (;4;) + (export "$imports" (table 0)) + (export "0" (func 5)) + (export "1" (func 6)) + (export "2" (func 7)) + (export "3" (func 8)) + ) + (core instance (;5;) (instantiate 2 + (with "" (instance 4)) + ) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/async-import/component.wit.print b/crates/wit-component/tests/components/async-import/component.wit.print new file mode 100644 index 0000000000..d820bfa480 --- /dev/null +++ b/crates/wit-component/tests/components/async-import/component.wit.print @@ -0,0 +1,6 @@ +package root:component; + +world root { + import foo:foo/bar; + import foo: func(s: string) -> string; +} diff --git a/crates/wit-component/tests/components/async-import/module.wat b/crates/wit-component/tests/components/async-import/module.wat new file mode 100644 index 0000000000..49b6715fa5 --- /dev/null +++ b/crates/wit-component/tests/components/async-import/module.wat @@ -0,0 +1,8 @@ +(module + (func (import "$root" "[async]foo") (param i32 i32) (result i32)) + (func (import "foo:foo/bar" "[async]foo") (param i32 i32) (result i32)) + (func (import "$root" "foo") (param i32 i32 i32)) + (func (import "foo:foo/bar" "foo") (param i32 i32 i32)) + (memory (export "memory") 1) + (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) diff --git a/crates/wit-component/tests/components/async-import/module.wit b/crates/wit-component/tests/components/async-import/module.wit new file mode 100644 index 0000000000..1f18c69192 --- /dev/null +++ b/crates/wit-component/tests/components/async-import/module.wit @@ -0,0 +1,10 @@ +package foo:foo; + +interface bar { + foo: func(s: string) -> string; +} + +world module { + import bar; + import foo: func(s: string) -> string; +} diff --git a/crates/wit-component/tests/components/async-streams-and-futures/component.wat b/crates/wit-component/tests/components/async-streams-and-futures/component.wat new file mode 100644 index 0000000000..d5ee978779 --- /dev/null +++ b/crates/wit-component/tests/components/async-streams-and-futures/component.wat @@ -0,0 +1,658 @@ +(component + (type (;0;) + (instance + (type (;0;) (future u32)) + (type (;1;) (future 0)) + (type (;2;) (stream string)) + (type (;3;) (func (param "x" 1) (param "y" u32) (result 2))) + (export (;0;) "foo" (func (type 3))) + ) + ) + (import "foo:foo/bar" (instance (;0;) (type 0))) + (type (;1;) (future u32)) + (type (;2;) (future 1)) + (type (;3;) (stream string)) + (type (;4;) (func (param "x" 2) (param "y" u32) (result 3))) + (import "foo" (func (;0;) (type 4))) + (core module (;0;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32 i32) (result i32))) + (type (;3;) (func (param i32) (result i32))) + (type (;4;) (func (param i32))) + (type (;5;) (func (param i32 i32))) + (type (;6;) (func (param i32 i32 i32 i32) (result i32))) + (import "$root" "[async]foo" (func (;0;) (type 0))) + (import "foo:foo/bar" "[async]foo" (func (;1;) (type 0))) + (import "[import-payload]$root" "[stream-new-2]foo" (func (;2;) (type 1))) + (import "[import-payload]$root" "[stream-read-2]foo" (func (;3;) (type 2))) + (import "[import-payload]$root" "[stream-write-2]foo" (func (;4;) (type 2))) + (import "[import-payload]$root" "[stream-cancel-read-2]foo" (func (;5;) (type 3))) + (import "[import-payload]$root" "[stream-cancel-write-2]foo" (func (;6;) (type 3))) + (import "[import-payload]$root" "[stream-close-readable-2]foo" (func (;7;) (type 4))) + (import "[import-payload]$root" "[stream-close-writable-2]foo" (func (;8;) (type 5))) + (import "[import-payload]foo:foo/bar" "[stream-new-2]foo" (func (;9;) (type 1))) + (import "[import-payload]foo:foo/bar" "[stream-read-2]foo" (func (;10;) (type 2))) + (import "[import-payload]foo:foo/bar" "[stream-write-2]foo" (func (;11;) (type 2))) + (import "[import-payload]foo:foo/bar" "[stream-cancel-read-2]foo" (func (;12;) (type 3))) + (import "[import-payload]foo:foo/bar" "[stream-cancel-write-2]foo" (func (;13;) (type 3))) + (import "[import-payload]foo:foo/bar" "[stream-close-readable-2]foo" (func (;14;) (type 4))) + (import "[import-payload]foo:foo/bar" "[stream-close-writable-2]foo" (func (;15;) (type 5))) + (import "[import-payload]$root" "[future-new-0]foo" (func (;16;) (type 1))) + (import "[import-payload]$root" "[future-read-0]foo" (func (;17;) (type 0))) + (import "[import-payload]$root" "[future-write-0]foo" (func (;18;) (type 0))) + (import "[import-payload]$root" "[future-cancel-read-0]foo" (func (;19;) (type 3))) + (import "[import-payload]$root" "[future-cancel-write-0]foo" (func (;20;) (type 3))) + (import "[import-payload]$root" "[future-close-readable-0]foo" (func (;21;) (type 4))) + (import "[import-payload]$root" "[future-close-writable-0]foo" (func (;22;) (type 5))) + (import "[import-payload]foo:foo/bar" "[future-new-0]foo" (func (;23;) (type 1))) + (import "[import-payload]foo:foo/bar" "[future-read-0]foo" (func (;24;) (type 0))) + (import "[import-payload]foo:foo/bar" "[future-write-0]foo" (func (;25;) (type 0))) + (import "[import-payload]foo:foo/bar" "[future-cancel-read-0]foo" (func (;26;) (type 3))) + (import "[import-payload]foo:foo/bar" "[future-cancel-write-0]foo" (func (;27;) (type 3))) + (import "[import-payload]foo:foo/bar" "[future-close-readable-0]foo" (func (;28;) (type 4))) + (import "[import-payload]foo:foo/bar" "[future-close-writable-0]foo" (func (;29;) (type 5))) + (import "[import-payload]$root" "[future-new-1]foo" (func (;30;) (type 1))) + (import "[import-payload]$root" "[future-read-1]foo" (func (;31;) (type 0))) + (import "[import-payload]$root" "[future-write-1]foo" (func (;32;) (type 0))) + (import "[import-payload]$root" "[future-cancel-read-1]foo" (func (;33;) (type 3))) + (import "[import-payload]$root" "[future-cancel-write-1]foo" (func (;34;) (type 3))) + (import "[import-payload]$root" "[future-close-readable-1]foo" (func (;35;) (type 4))) + (import "[import-payload]$root" "[future-close-writable-1]foo" (func (;36;) (type 5))) + (import "[import-payload]foo:foo/bar" "[future-new-1]foo" (func (;37;) (type 1))) + (import "[import-payload]foo:foo/bar" "[future-read-1]foo" (func (;38;) (type 0))) + (import "[import-payload]foo:foo/bar" "[future-write-1]foo" (func (;39;) (type 0))) + (import "[import-payload]foo:foo/bar" "[future-cancel-read-1]foo" (func (;40;) (type 3))) + (import "[import-payload]foo:foo/bar" "[future-cancel-write-1]foo" (func (;41;) (type 3))) + (import "[import-payload]foo:foo/bar" "[future-close-readable-1]foo" (func (;42;) (type 4))) + (import "[import-payload]foo:foo/bar" "[future-close-writable-1]foo" (func (;43;) (type 5))) + (import "[export-payload]$root" "[stream-new-2]foo" (func (;44;) (type 1))) + (import "[export-payload]$root" "[stream-read-2]foo" (func (;45;) (type 2))) + (import "[export-payload]$root" "[stream-write-2]foo" (func (;46;) (type 2))) + (import "[export-payload]$root" "[stream-cancel-read-2]foo" (func (;47;) (type 3))) + (import "[export-payload]$root" "[stream-cancel-write-2]foo" (func (;48;) (type 3))) + (import "[export-payload]$root" "[stream-close-readable-2]foo" (func (;49;) (type 4))) + (import "[export-payload]$root" "[stream-close-writable-2]foo" (func (;50;) (type 5))) + (import "[export-payload]foo:foo/bar" "[stream-new-2]foo" (func (;51;) (type 1))) + (import "[export-payload]foo:foo/bar" "[stream-read-2]foo" (func (;52;) (type 2))) + (import "[export-payload]foo:foo/bar" "[stream-write-2]foo" (func (;53;) (type 2))) + (import "[export-payload]foo:foo/bar" "[stream-cancel-read-2]foo" (func (;54;) (type 3))) + (import "[export-payload]foo:foo/bar" "[stream-cancel-write-2]foo" (func (;55;) (type 3))) + (import "[export-payload]foo:foo/bar" "[stream-close-readable-2]foo" (func (;56;) (type 4))) + (import "[export-payload]foo:foo/bar" "[stream-close-writable-2]foo" (func (;57;) (type 5))) + (import "[export-payload]$root" "[future-new-0]foo" (func (;58;) (type 1))) + (import "[export-payload]$root" "[future-read-0]foo" (func (;59;) (type 0))) + (import "[export-payload]$root" "[future-write-0]foo" (func (;60;) (type 0))) + (import "[export-payload]$root" "[future-cancel-read-0]foo" (func (;61;) (type 3))) + (import "[export-payload]$root" "[future-cancel-write-0]foo" (func (;62;) (type 3))) + (import "[export-payload]$root" "[future-close-readable-0]foo" (func (;63;) (type 4))) + (import "[export-payload]$root" "[future-close-writable-0]foo" (func (;64;) (type 5))) + (import "[export-payload]foo:foo/bar" "[future-new-0]foo" (func (;65;) (type 1))) + (import "[export-payload]foo:foo/bar" "[future-read-0]foo" (func (;66;) (type 0))) + (import "[export-payload]foo:foo/bar" "[future-write-0]foo" (func (;67;) (type 0))) + (import "[export-payload]foo:foo/bar" "[future-cancel-read-0]foo" (func (;68;) (type 3))) + (import "[export-payload]foo:foo/bar" "[future-cancel-write-0]foo" (func (;69;) (type 3))) + (import "[export-payload]foo:foo/bar" "[future-close-readable-0]foo" (func (;70;) (type 4))) + (import "[export-payload]foo:foo/bar" "[future-close-writable-0]foo" (func (;71;) (type 5))) + (import "[export-payload]$root" "[future-new-1]foo" (func (;72;) (type 1))) + (import "[export-payload]$root" "[future-read-1]foo" (func (;73;) (type 0))) + (import "[export-payload]$root" "[future-write-1]foo" (func (;74;) (type 0))) + (import "[export-payload]$root" "[future-cancel-read-1]foo" (func (;75;) (type 3))) + (import "[export-payload]$root" "[future-cancel-write-1]foo" (func (;76;) (type 3))) + (import "[export-payload]$root" "[future-close-readable-1]foo" (func (;77;) (type 4))) + (import "[export-payload]$root" "[future-close-writable-1]foo" (func (;78;) (type 5))) + (import "[export-payload]foo:foo/bar" "[future-new-1]foo" (func (;79;) (type 1))) + (import "[export-payload]foo:foo/bar" "[future-read-1]foo" (func (;80;) (type 0))) + (import "[export-payload]foo:foo/bar" "[future-write-1]foo" (func (;81;) (type 0))) + (import "[export-payload]foo:foo/bar" "[future-cancel-read-1]foo" (func (;82;) (type 3))) + (import "[export-payload]foo:foo/bar" "[future-cancel-write-1]foo" (func (;83;) (type 3))) + (import "[export-payload]foo:foo/bar" "[future-close-readable-1]foo" (func (;84;) (type 4))) + (import "[export-payload]foo:foo/bar" "[future-close-writable-1]foo" (func (;85;) (type 5))) + (memory (;0;) 1) + (export "[async-stackful]foo" (func 86)) + (export "[async-stackful]foo:foo/bar#foo" (func 87)) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 88)) + (func (;86;) (type 5) (param i32 i32) + unreachable + ) + (func (;87;) (type 5) (param i32 i32) + unreachable + ) + (func (;88;) (type 6) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core module (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32) (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (table (;0;) 26 26 funcref) + (export "0" (func $"indirect-$root-[async]foo")) + (export "1" (func $"indirect-foo:foo/bar-[async]foo")) + (export "2" (func $"[import-payload]$root-[stream-read-2]foo")) + (export "3" (func $"[import-payload]$root-[stream-write-2]foo")) + (export "4" (func $"[import-payload]$root-[future-read-0]foo")) + (export "5" (func $"[import-payload]$root-[future-write-0]foo")) + (export "6" (func $"[import-payload]$root-[future-read-1]foo")) + (export "7" (func $"[import-payload]$root-[future-write-1]foo")) + (export "8" (func $"[import-payload]foo:foo/bar-[stream-read-2]foo")) + (export "9" (func $"[import-payload]foo:foo/bar-[stream-write-2]foo")) + (export "10" (func $"[import-payload]foo:foo/bar-[future-read-0]foo")) + (export "11" (func $"[import-payload]foo:foo/bar-[future-write-0]foo")) + (export "12" (func $"[import-payload]foo:foo/bar-[future-read-1]foo")) + (export "13" (func $"[import-payload]foo:foo/bar-[future-write-1]foo")) + (export "14" (func $"[export-payload]$root-[stream-read-2]foo")) + (export "15" (func $"[export-payload]$root-[stream-write-2]foo")) + (export "16" (func $"[export-payload]$root-[future-read-0]foo")) + (export "17" (func $"[export-payload]$root-[future-write-0]foo")) + (export "18" (func $"[export-payload]$root-[future-read-1]foo")) + (export "19" (func $"[export-payload]$root-[future-write-1]foo")) + (export "20" (func $"[export-payload]foo:foo/bar-[stream-read-2]foo")) + (export "21" (func $"[export-payload]foo:foo/bar-[stream-write-2]foo")) + (export "22" (func $"[export-payload]foo:foo/bar-[future-read-0]foo")) + (export "23" (func $"[export-payload]foo:foo/bar-[future-write-0]foo")) + (export "24" (func $"[export-payload]foo:foo/bar-[future-read-1]foo")) + (export "25" (func $"[export-payload]foo:foo/bar-[future-write-1]foo")) + (export "$imports" (table 0)) + (func $"indirect-$root-[async]foo" (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 0 + call_indirect (type 0) + ) + (func $"indirect-foo:foo/bar-[async]foo" (;1;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 1 + call_indirect (type 0) + ) + (func $"[import-payload]$root-[stream-read-2]foo" (;2;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 2 + call_indirect (type 1) + ) + (func $"[import-payload]$root-[stream-write-2]foo" (;3;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 3 + call_indirect (type 1) + ) + (func $"[import-payload]$root-[future-read-0]foo" (;4;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 4 + call_indirect (type 2) + ) + (func $"[import-payload]$root-[future-write-0]foo" (;5;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 5 + call_indirect (type 2) + ) + (func $"[import-payload]$root-[future-read-1]foo" (;6;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 6 + call_indirect (type 2) + ) + (func $"[import-payload]$root-[future-write-1]foo" (;7;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 7 + call_indirect (type 2) + ) + (func $"[import-payload]foo:foo/bar-[stream-read-2]foo" (;8;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 8 + call_indirect (type 1) + ) + (func $"[import-payload]foo:foo/bar-[stream-write-2]foo" (;9;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 9 + call_indirect (type 1) + ) + (func $"[import-payload]foo:foo/bar-[future-read-0]foo" (;10;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 10 + call_indirect (type 2) + ) + (func $"[import-payload]foo:foo/bar-[future-write-0]foo" (;11;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 11 + call_indirect (type 2) + ) + (func $"[import-payload]foo:foo/bar-[future-read-1]foo" (;12;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 12 + call_indirect (type 2) + ) + (func $"[import-payload]foo:foo/bar-[future-write-1]foo" (;13;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 13 + call_indirect (type 2) + ) + (func $"[export-payload]$root-[stream-read-2]foo" (;14;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 14 + call_indirect (type 1) + ) + (func $"[export-payload]$root-[stream-write-2]foo" (;15;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 15 + call_indirect (type 1) + ) + (func $"[export-payload]$root-[future-read-0]foo" (;16;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 16 + call_indirect (type 2) + ) + (func $"[export-payload]$root-[future-write-0]foo" (;17;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 17 + call_indirect (type 2) + ) + (func $"[export-payload]$root-[future-read-1]foo" (;18;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 18 + call_indirect (type 2) + ) + (func $"[export-payload]$root-[future-write-1]foo" (;19;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 19 + call_indirect (type 2) + ) + (func $"[export-payload]foo:foo/bar-[stream-read-2]foo" (;20;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 20 + call_indirect (type 1) + ) + (func $"[export-payload]foo:foo/bar-[stream-write-2]foo" (;21;) (type 1) (param i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 21 + call_indirect (type 1) + ) + (func $"[export-payload]foo:foo/bar-[future-read-0]foo" (;22;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 22 + call_indirect (type 2) + ) + (func $"[export-payload]foo:foo/bar-[future-write-0]foo" (;23;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 23 + call_indirect (type 2) + ) + (func $"[export-payload]foo:foo/bar-[future-read-1]foo" (;24;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 24 + call_indirect (type 2) + ) + (func $"[export-payload]foo:foo/bar-[future-write-1]foo" (;25;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 25 + call_indirect (type 2) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;2;) + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32) (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 0))) + (import "" "2" (func (;2;) (type 1))) + (import "" "3" (func (;3;) (type 1))) + (import "" "4" (func (;4;) (type 2))) + (import "" "5" (func (;5;) (type 2))) + (import "" "6" (func (;6;) (type 2))) + (import "" "7" (func (;7;) (type 2))) + (import "" "8" (func (;8;) (type 1))) + (import "" "9" (func (;9;) (type 1))) + (import "" "10" (func (;10;) (type 2))) + (import "" "11" (func (;11;) (type 2))) + (import "" "12" (func (;12;) (type 2))) + (import "" "13" (func (;13;) (type 2))) + (import "" "14" (func (;14;) (type 1))) + (import "" "15" (func (;15;) (type 1))) + (import "" "16" (func (;16;) (type 2))) + (import "" "17" (func (;17;) (type 2))) + (import "" "18" (func (;18;) (type 2))) + (import "" "19" (func (;19;) (type 2))) + (import "" "20" (func (;20;) (type 1))) + (import "" "21" (func (;21;) (type 1))) + (import "" "22" (func (;22;) (type 2))) + (import "" "23" (func (;23;) (type 2))) + (import "" "24" (func (;24;) (type 2))) + (import "" "25" (func (;25;) (type 2))) + (import "" "$imports" (table (;0;) 26 26 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 1)) + (alias core export 0 "0" (core func (;0;))) + (core instance (;1;) + (export "[async]foo" (func 0)) + ) + (alias core export 0 "1" (core func (;1;))) + (core instance (;2;) + (export "[async]foo" (func 1)) + ) + (core func (;2;) (canon stream.new 3)) + (alias core export 0 "2" (core func (;3;))) + (alias core export 0 "3" (core func (;4;))) + (core func (;5;) (canon stream.cancel-read 3)) + (core func (;6;) (canon stream.cancel-write 3)) + (core func (;7;) (canon stream.close-readable 3)) + (core func (;8;) (canon stream.close-writable 3)) + (core func (;9;) (canon future.new 1)) + (alias core export 0 "4" (core func (;10;))) + (alias core export 0 "5" (core func (;11;))) + (core func (;12;) (canon future.cancel-read 1)) + (core func (;13;) (canon future.cancel-write 1)) + (core func (;14;) (canon future.close-readable 1)) + (core func (;15;) (canon future.close-writable 1)) + (core func (;16;) (canon future.new 2)) + (alias core export 0 "6" (core func (;17;))) + (alias core export 0 "7" (core func (;18;))) + (core func (;19;) (canon future.cancel-read 2)) + (core func (;20;) (canon future.cancel-write 2)) + (core func (;21;) (canon future.close-readable 2)) + (core func (;22;) (canon future.close-writable 2)) + (core instance (;3;) + (export "[stream-new-2]foo" (func 2)) + (export "[stream-read-2]foo" (func 3)) + (export "[stream-write-2]foo" (func 4)) + (export "[stream-cancel-read-2]foo" (func 5)) + (export "[stream-cancel-write-2]foo" (func 6)) + (export "[stream-close-readable-2]foo" (func 7)) + (export "[stream-close-writable-2]foo" (func 8)) + (export "[future-new-0]foo" (func 9)) + (export "[future-read-0]foo" (func 10)) + (export "[future-write-0]foo" (func 11)) + (export "[future-cancel-read-0]foo" (func 12)) + (export "[future-cancel-write-0]foo" (func 13)) + (export "[future-close-readable-0]foo" (func 14)) + (export "[future-close-writable-0]foo" (func 15)) + (export "[future-new-1]foo" (func 16)) + (export "[future-read-1]foo" (func 17)) + (export "[future-write-1]foo" (func 18)) + (export "[future-cancel-read-1]foo" (func 19)) + (export "[future-cancel-write-1]foo" (func 20)) + (export "[future-close-readable-1]foo" (func 21)) + (export "[future-close-writable-1]foo" (func 22)) + ) + (type (;5;) (stream string)) + (core func (;23;) (canon stream.new 5)) + (alias core export 0 "8" (core func (;24;))) + (alias core export 0 "9" (core func (;25;))) + (core func (;26;) (canon stream.cancel-read 5)) + (core func (;27;) (canon stream.cancel-write 5)) + (core func (;28;) (canon stream.close-readable 5)) + (core func (;29;) (canon stream.close-writable 5)) + (type (;6;) (future u32)) + (core func (;30;) (canon future.new 6)) + (alias core export 0 "10" (core func (;31;))) + (alias core export 0 "11" (core func (;32;))) + (core func (;33;) (canon future.cancel-read 6)) + (core func (;34;) (canon future.cancel-write 6)) + (core func (;35;) (canon future.close-readable 6)) + (core func (;36;) (canon future.close-writable 6)) + (type (;7;) (future 6)) + (core func (;37;) (canon future.new 7)) + (alias core export 0 "12" (core func (;38;))) + (alias core export 0 "13" (core func (;39;))) + (core func (;40;) (canon future.cancel-read 7)) + (core func (;41;) (canon future.cancel-write 7)) + (core func (;42;) (canon future.close-readable 7)) + (core func (;43;) (canon future.close-writable 7)) + (core instance (;4;) + (export "[stream-new-2]foo" (func 23)) + (export "[stream-read-2]foo" (func 24)) + (export "[stream-write-2]foo" (func 25)) + (export "[stream-cancel-read-2]foo" (func 26)) + (export "[stream-cancel-write-2]foo" (func 27)) + (export "[stream-close-readable-2]foo" (func 28)) + (export "[stream-close-writable-2]foo" (func 29)) + (export "[future-new-0]foo" (func 30)) + (export "[future-read-0]foo" (func 31)) + (export "[future-write-0]foo" (func 32)) + (export "[future-cancel-read-0]foo" (func 33)) + (export "[future-cancel-write-0]foo" (func 34)) + (export "[future-close-readable-0]foo" (func 35)) + (export "[future-close-writable-0]foo" (func 36)) + (export "[future-new-1]foo" (func 37)) + (export "[future-read-1]foo" (func 38)) + (export "[future-write-1]foo" (func 39)) + (export "[future-cancel-read-1]foo" (func 40)) + (export "[future-cancel-write-1]foo" (func 41)) + (export "[future-close-readable-1]foo" (func 42)) + (export "[future-close-writable-1]foo" (func 43)) + ) + (type (;8;) (stream string)) + (core func (;44;) (canon stream.new 8)) + (alias core export 0 "14" (core func (;45;))) + (alias core export 0 "15" (core func (;46;))) + (core func (;47;) (canon stream.cancel-read 8)) + (core func (;48;) (canon stream.cancel-write 8)) + (core func (;49;) (canon stream.close-readable 8)) + (core func (;50;) (canon stream.close-writable 8)) + (type (;9;) (future u32)) + (core func (;51;) (canon future.new 9)) + (alias core export 0 "16" (core func (;52;))) + (alias core export 0 "17" (core func (;53;))) + (core func (;54;) (canon future.cancel-read 9)) + (core func (;55;) (canon future.cancel-write 9)) + (core func (;56;) (canon future.close-readable 9)) + (core func (;57;) (canon future.close-writable 9)) + (type (;10;) (future 9)) + (core func (;58;) (canon future.new 10)) + (alias core export 0 "18" (core func (;59;))) + (alias core export 0 "19" (core func (;60;))) + (core func (;61;) (canon future.cancel-read 10)) + (core func (;62;) (canon future.cancel-write 10)) + (core func (;63;) (canon future.close-readable 10)) + (core func (;64;) (canon future.close-writable 10)) + (core instance (;5;) + (export "[stream-new-2]foo" (func 44)) + (export "[stream-read-2]foo" (func 45)) + (export "[stream-write-2]foo" (func 46)) + (export "[stream-cancel-read-2]foo" (func 47)) + (export "[stream-cancel-write-2]foo" (func 48)) + (export "[stream-close-readable-2]foo" (func 49)) + (export "[stream-close-writable-2]foo" (func 50)) + (export "[future-new-0]foo" (func 51)) + (export "[future-read-0]foo" (func 52)) + (export "[future-write-0]foo" (func 53)) + (export "[future-cancel-read-0]foo" (func 54)) + (export "[future-cancel-write-0]foo" (func 55)) + (export "[future-close-readable-0]foo" (func 56)) + (export "[future-close-writable-0]foo" (func 57)) + (export "[future-new-1]foo" (func 58)) + (export "[future-read-1]foo" (func 59)) + (export "[future-write-1]foo" (func 60)) + (export "[future-cancel-read-1]foo" (func 61)) + (export "[future-cancel-write-1]foo" (func 62)) + (export "[future-close-readable-1]foo" (func 63)) + (export "[future-close-writable-1]foo" (func 64)) + ) + (type (;11;) (stream string)) + (core func (;65;) (canon stream.new 11)) + (alias core export 0 "20" (core func (;66;))) + (alias core export 0 "21" (core func (;67;))) + (core func (;68;) (canon stream.cancel-read 11)) + (core func (;69;) (canon stream.cancel-write 11)) + (core func (;70;) (canon stream.close-readable 11)) + (core func (;71;) (canon stream.close-writable 11)) + (type (;12;) (future u32)) + (core func (;72;) (canon future.new 12)) + (alias core export 0 "22" (core func (;73;))) + (alias core export 0 "23" (core func (;74;))) + (core func (;75;) (canon future.cancel-read 12)) + (core func (;76;) (canon future.cancel-write 12)) + (core func (;77;) (canon future.close-readable 12)) + (core func (;78;) (canon future.close-writable 12)) + (type (;13;) (future 12)) + (core func (;79;) (canon future.new 13)) + (alias core export 0 "24" (core func (;80;))) + (alias core export 0 "25" (core func (;81;))) + (core func (;82;) (canon future.cancel-read 13)) + (core func (;83;) (canon future.cancel-write 13)) + (core func (;84;) (canon future.close-readable 13)) + (core func (;85;) (canon future.close-writable 13)) + (core instance (;6;) + (export "[stream-new-2]foo" (func 65)) + (export "[stream-read-2]foo" (func 66)) + (export "[stream-write-2]foo" (func 67)) + (export "[stream-cancel-read-2]foo" (func 68)) + (export "[stream-cancel-write-2]foo" (func 69)) + (export "[stream-close-readable-2]foo" (func 70)) + (export "[stream-close-writable-2]foo" (func 71)) + (export "[future-new-0]foo" (func 72)) + (export "[future-read-0]foo" (func 73)) + (export "[future-write-0]foo" (func 74)) + (export "[future-cancel-read-0]foo" (func 75)) + (export "[future-cancel-write-0]foo" (func 76)) + (export "[future-close-readable-0]foo" (func 77)) + (export "[future-close-writable-0]foo" (func 78)) + (export "[future-new-1]foo" (func 79)) + (export "[future-read-1]foo" (func 80)) + (export "[future-write-1]foo" (func 81)) + (export "[future-cancel-read-1]foo" (func 82)) + (export "[future-cancel-write-1]foo" (func 83)) + (export "[future-close-readable-1]foo" (func 84)) + (export "[future-close-writable-1]foo" (func 85)) + ) + (core instance (;7;) (instantiate 0 + (with "$root" (instance 1)) + (with "foo:foo/bar" (instance 2)) + (with "[import-payload]$root" (instance 3)) + (with "[import-payload]foo:foo/bar" (instance 4)) + (with "[export-payload]$root" (instance 5)) + (with "[export-payload]foo:foo/bar" (instance 6)) + ) + ) + (alias core export 7 "memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (alias core export 7 "cabi_realloc" (core func (;86;))) + (core func (;87;) (canon lower (func 0) (memory 0) async)) + (alias export 0 "foo" (func (;1;))) + (core func (;88;) (canon lower (func 1) (memory 0) async)) + (core func (;89;) (canon stream.read 3 (memory 0) (realloc 86) string-encoding=utf8)) + (core func (;90;) (canon stream.write 3 (memory 0) string-encoding=utf8)) + (core func (;91;) (canon future.read 1 (memory 0))) + (core func (;92;) (canon future.write 1 (memory 0))) + (core func (;93;) (canon future.read 2 (memory 0))) + (core func (;94;) (canon future.write 2 (memory 0))) + (core func (;95;) (canon stream.read 5 (memory 0) (realloc 86) string-encoding=utf8)) + (core func (;96;) (canon stream.write 5 (memory 0) string-encoding=utf8)) + (core func (;97;) (canon future.read 6 (memory 0))) + (core func (;98;) (canon future.write 6 (memory 0))) + (core func (;99;) (canon future.read 7 (memory 0))) + (core func (;100;) (canon future.write 7 (memory 0))) + (core func (;101;) (canon stream.read 8 (memory 0) (realloc 86) string-encoding=utf8)) + (core func (;102;) (canon stream.write 8 (memory 0) string-encoding=utf8)) + (core func (;103;) (canon future.read 9 (memory 0))) + (core func (;104;) (canon future.write 9 (memory 0))) + (core func (;105;) (canon future.read 10 (memory 0))) + (core func (;106;) (canon future.write 10 (memory 0))) + (core func (;107;) (canon stream.read 11 (memory 0) (realloc 86) string-encoding=utf8)) + (core func (;108;) (canon stream.write 11 (memory 0) string-encoding=utf8)) + (core func (;109;) (canon future.read 12 (memory 0))) + (core func (;110;) (canon future.write 12 (memory 0))) + (core func (;111;) (canon future.read 13 (memory 0))) + (core func (;112;) (canon future.write 13 (memory 0))) + (core instance (;8;) + (export "$imports" (table 0)) + (export "0" (func 87)) + (export "1" (func 88)) + (export "2" (func 89)) + (export "3" (func 90)) + (export "4" (func 91)) + (export "5" (func 92)) + (export "6" (func 93)) + (export "7" (func 94)) + (export "8" (func 95)) + (export "9" (func 96)) + (export "10" (func 97)) + (export "11" (func 98)) + (export "12" (func 99)) + (export "13" (func 100)) + (export "14" (func 101)) + (export "15" (func 102)) + (export "16" (func 103)) + (export "17" (func 104)) + (export "18" (func 105)) + (export "19" (func 106)) + (export "20" (func 107)) + (export "21" (func 108)) + (export "22" (func 109)) + (export "23" (func 110)) + (export "24" (func 111)) + (export "25" (func 112)) + ) + (core instance (;9;) (instantiate 2 + (with "" (instance 8)) + ) + ) + (alias core export 7 "[async-stackful]foo" (core func (;113;))) + (func (;2;) (type 4) (canon lift (core func 113) async)) + (export (;3;) "foo" (func 2)) + (type (;14;) (func (param "x" 13) (param "y" u32) (result 11))) + (alias core export 7 "[async-stackful]foo:foo/bar#foo" (core func (;114;))) + (func (;4;) (type 14) (canon lift (core func 114) async)) + (component (;0;) + (type (;0;) (future u32)) + (type (;1;) (future 0)) + (type (;2;) (stream string)) + (type (;3;) (func (param "x" 1) (param "y" u32) (result 2))) + (import "import-func-foo" (func (;0;) (type 3))) + (type (;4;) (future u32)) + (type (;5;) (future 4)) + (type (;6;) (stream string)) + (type (;7;) (func (param "x" 5) (param "y" u32) (result 6))) + (export (;1;) "foo" (func 0) (func (type 7))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-foo" (func 4)) + ) + ) + (export (;2;) "foo:foo/bar" (instance 1)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/async-streams-and-futures/component.wit.print b/crates/wit-component/tests/components/async-streams-and-futures/component.wit.print new file mode 100644 index 0000000000..9dc12adbd9 --- /dev/null +++ b/crates/wit-component/tests/components/async-streams-and-futures/component.wit.print @@ -0,0 +1,9 @@ +package root:component; + +world root { + import foo:foo/bar; + import foo: func(x: future>, y: u32) -> stream; + + export foo: func(x: future>, y: u32) -> stream; + export foo:foo/bar; +} diff --git a/crates/wit-component/tests/components/async-streams-and-futures/module.wat b/crates/wit-component/tests/components/async-streams-and-futures/module.wat new file mode 100644 index 0000000000..a62c67a051 --- /dev/null +++ b/crates/wit-component/tests/components/async-streams-and-futures/module.wat @@ -0,0 +1,92 @@ +(module + (func (import "$root" "[async]foo") (param i32 i32) (result i32)) + (func (import "foo:foo/bar" "[async]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]$root" "[stream-new-2]foo") (result i32)) + (func (import "[import-payload]$root" "[stream-read-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[import-payload]$root" "[stream-write-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[import-payload]$root" "[stream-cancel-read-2]foo") (param i32) (result i32)) + (func (import "[import-payload]$root" "[stream-cancel-write-2]foo") (param i32) (result i32)) + (func (import "[import-payload]$root" "[stream-close-readable-2]foo") (param i32)) + (func (import "[import-payload]$root" "[stream-close-writable-2]foo") (param i32 i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-new-2]foo") (result i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-read-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-write-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-cancel-read-2]foo") (param i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-cancel-write-2]foo") (param i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-close-readable-2]foo") (param i32)) + (func (import "[import-payload]foo:foo/bar" "[stream-close-writable-2]foo") (param i32 i32)) + (func (import "[import-payload]$root" "[future-new-0]foo") (result i32)) + (func (import "[import-payload]$root" "[future-read-0]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]$root" "[future-write-0]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]$root" "[future-cancel-read-0]foo") (param i32) (result i32)) + (func (import "[import-payload]$root" "[future-cancel-write-0]foo") (param i32) (result i32)) + (func (import "[import-payload]$root" "[future-close-readable-0]foo") (param i32)) + (func (import "[import-payload]$root" "[future-close-writable-0]foo") (param i32 i32)) + (func (import "[import-payload]foo:foo/bar" "[future-new-0]foo") (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-read-0]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-write-0]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-cancel-read-0]foo") (param i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-cancel-write-0]foo") (param i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-close-readable-0]foo") (param i32)) + (func (import "[import-payload]foo:foo/bar" "[future-close-writable-0]foo") (param i32 i32)) + (func (import "[import-payload]$root" "[future-new-1]foo") (result i32)) + (func (import "[import-payload]$root" "[future-read-1]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]$root" "[future-write-1]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]$root" "[future-cancel-read-1]foo") (param i32) (result i32)) + (func (import "[import-payload]$root" "[future-cancel-write-1]foo") (param i32) (result i32)) + (func (import "[import-payload]$root" "[future-close-readable-1]foo") (param i32)) + (func (import "[import-payload]$root" "[future-close-writable-1]foo") (param i32 i32)) + (func (import "[import-payload]foo:foo/bar" "[future-new-1]foo") (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-read-1]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-write-1]foo") (param i32 i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-cancel-read-1]foo") (param i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-cancel-write-1]foo") (param i32) (result i32)) + (func (import "[import-payload]foo:foo/bar" "[future-close-readable-1]foo") (param i32)) + (func (import "[import-payload]foo:foo/bar" "[future-close-writable-1]foo") (param i32 i32)) + (func (import "[export-payload]$root" "[stream-new-2]foo") (result i32)) + (func (import "[export-payload]$root" "[stream-read-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[export-payload]$root" "[stream-write-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[export-payload]$root" "[stream-cancel-read-2]foo") (param i32) (result i32)) + (func (import "[export-payload]$root" "[stream-cancel-write-2]foo") (param i32) (result i32)) + (func (import "[export-payload]$root" "[stream-close-readable-2]foo") (param i32)) + (func (import "[export-payload]$root" "[stream-close-writable-2]foo") (param i32 i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-new-2]foo") (result i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-read-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-write-2]foo") (param i32 i32 i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-cancel-read-2]foo") (param i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-cancel-write-2]foo") (param i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-close-readable-2]foo") (param i32)) + (func (import "[export-payload]foo:foo/bar" "[stream-close-writable-2]foo") (param i32 i32)) + (func (import "[export-payload]$root" "[future-new-0]foo") (result i32)) + (func (import "[export-payload]$root" "[future-read-0]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]$root" "[future-write-0]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]$root" "[future-cancel-read-0]foo") (param i32) (result i32)) + (func (import "[export-payload]$root" "[future-cancel-write-0]foo") (param i32) (result i32)) + (func (import "[export-payload]$root" "[future-close-readable-0]foo") (param i32)) + (func (import "[export-payload]$root" "[future-close-writable-0]foo") (param i32 i32)) + (func (import "[export-payload]foo:foo/bar" "[future-new-0]foo") (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-read-0]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-write-0]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-cancel-read-0]foo") (param i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-cancel-write-0]foo") (param i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-close-readable-0]foo") (param i32)) + (func (import "[export-payload]foo:foo/bar" "[future-close-writable-0]foo") (param i32 i32)) + (func (import "[export-payload]$root" "[future-new-1]foo") (result i32)) + (func (import "[export-payload]$root" "[future-read-1]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]$root" "[future-write-1]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]$root" "[future-cancel-read-1]foo") (param i32) (result i32)) + (func (import "[export-payload]$root" "[future-cancel-write-1]foo") (param i32) (result i32)) + (func (import "[export-payload]$root" "[future-close-readable-1]foo") (param i32)) + (func (import "[export-payload]$root" "[future-close-writable-1]foo") (param i32 i32)) + (func (import "[export-payload]foo:foo/bar" "[future-new-1]foo") (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-read-1]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-write-1]foo") (param i32 i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-cancel-read-1]foo") (param i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-cancel-write-1]foo") (param i32) (result i32)) + (func (import "[export-payload]foo:foo/bar" "[future-close-readable-1]foo") (param i32)) + (func (import "[export-payload]foo:foo/bar" "[future-close-writable-1]foo") (param i32 i32)) + (func (export "[async-stackful]foo") (param i32 i32) unreachable) + (func (export "[async-stackful]foo:foo/bar#foo") (param i32 i32) unreachable) + (memory (export "memory") 1) + (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) diff --git a/crates/wit-component/tests/components/async-streams-and-futures/module.wit b/crates/wit-component/tests/components/async-streams-and-futures/module.wit new file mode 100644 index 0000000000..4f8b0bb7f4 --- /dev/null +++ b/crates/wit-component/tests/components/async-streams-and-futures/module.wit @@ -0,0 +1,12 @@ +package foo:foo; + +interface bar { + foo: func(x: future>, y: u32) -> stream; +} + +world module { + import bar; + import foo: func(x: future>, y: u32) -> stream; + export bar; + export foo: func(x: future>, y: u32) -> stream; +} diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs index db5ecd29f1..a31c34a10b 100644 --- a/crates/wit-encoder/src/from_parser.rs +++ b/crates/wit-encoder/src/from_parser.rs @@ -228,12 +228,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::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)); @@ -267,6 +268,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, @@ -300,12 +305,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::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 8370fe9ab4..583e39ab56 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), + ErrorContext, 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::ErrorContext => write!(f, "error-context"), } } } diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml index 3d801a522f..3084850dee 100644 --- a/crates/wit-parser/Cargo.toml +++ b/crates/wit-parser/Cargo.toml @@ -25,7 +25,7 @@ log = { workspace = true } semver = { workspace = true } serde = { workspace = true, optional = true } serde_derive = { workspace = true, optional = true } -wasmparser = { workspace = true, optional = true, features = ['validate', 'component-model'] } +wasmparser = { workspace = true, optional = true, features = ['validate', 'component-model', 'features'] } serde_json = { workspace = true, optional = true } wat = { workspace = true, optional = true } diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index f3d7fc82ed..35cf94d827 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -121,12 +121,15 @@ 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, + GuestExportAsyncStackful, } impl Resolve { @@ -135,6 +138,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 +163,12 @@ impl Resolve { } else { if matches!( (&func.kind, variant), - (crate::FunctionKind::Method(_), AbiVariant::GuestExport) + ( + crate::FunctionKind::Method(_), + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful + ) ) { // Guest exported methods always receive resource rep as first argument // @@ -164,6 +181,26 @@ impl Resolve { } } + match variant { + AbiVariant::GuestExportAsync => { + return WasmSignature { + params, + indirect_params, + results: vec![WasmType::Pointer], + retptr: false, + }; + } + AbiVariant::GuestExportAsyncStackful => { + return WasmSignature { + params, + indirect_params, + results: Vec::new(), + retptr: false, + }; + } + _ => {} + } + let mut results = Vec::new(); for ty in func.results.iter_types() { self.push_flat(ty, &mut results) @@ -185,6 +222,7 @@ impl Resolve { AbiVariant::GuestExport => { results.push(WasmType::Pointer); } + _ => unreachable!(), } } @@ -274,6 +312,10 @@ impl Resolve { result.push(WasmType::I32); } + TypeDefKind::ErrorContext => { + 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..b3e3033e58 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>), + ErrorContext(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,20 @@ 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-context + Some((span, Token::ErrorContext)) => Ok(Type::ErrorContext(span)), + // own Some((_span, Token::Own)) => { tokens.expect(Token::LessThan)?; @@ -1471,7 +1462,8 @@ impl<'a> Type<'a> { | Type::F32(span) | Type::F64(span) | Type::Char(span) - | Type::String(span) => *span, + | Type::String(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 a3a59cb945..ae70352397 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, + ErrorContext, List, Underscore, As, @@ -297,6 +298,7 @@ impl<'a> Tokenizer<'a> { "result" => Result_, "future" => Future, "stream" => Stream, + "error-context" => ErrorContext, "list" => List, "_" => Underscore, "as" => As, @@ -550,6 +552,7 @@ impl Token { Result_ => "keyword `result`", Future => "keyword `future`", Stream => "keyword `stream`", + 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 f788c7b6bf..c4ad6ecca1 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), + ErrorContext, } 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::ErrorContext(_) => TypeDefKind::ErrorContext, }) } @@ -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::ErrorContext => Key::ErrorContext, 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::ErrorContext(_) => {} 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..0dbb04147d 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -13,6 +13,7 @@ use wasmparser::{ types, types::Types, ComponentExternalKind, Parser, Payload, PrimitiveValType, ValidPayload, Validator, + WasmFeatures, }; /// Represents information about a decoded WebAssembly component. @@ -47,7 +48,7 @@ impl ComponentInfo { /// Creates a new component info by parsing the given WebAssembly component bytes. fn from_reader(mut reader: impl Read) -> Result { - let mut validator = Validator::new(); + let mut validator = Validator::new_with_features(WasmFeatures::all()); let mut externs = Vec::new(); let mut depth = 1; let mut types = None; @@ -400,6 +401,7 @@ pub fn decode_reader(reader: impl Read) -> Result { Ok(DecodedWasm::Component(resolve, world)) } } + /// Decodes an in-memory WebAssembly binary into a WIT [`Resolve`] and /// associated metadata. /// @@ -1260,15 +1262,16 @@ impl WitPackageDecoder<'_> { | TypeDefKind::Tuple(_) | TypeDefKind::Option(_) | TypeDefKind::Result(_) - | TypeDefKind::Handle(_) => {} + | TypeDefKind::Handle(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::ErrorContext => {} 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 +1396,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::ErrorContext => Ok(TypeDefKind::ErrorContext), } } @@ -1663,11 +1674,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::ErrorContext => Ok(()), } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 9a0cc6f4be..09d38dc401 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), + ErrorContext, 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::ErrorContext => "error-context", 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,84 @@ 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) { + let Type::Id(id) = ty else { + return; + }; + + match &resolve.types[id].kind { + TypeDefKind::Resource + | TypeDefKind::Handle(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::ErrorContext => {} + 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, @@ -1072,4 +1145,43 @@ mod test { assert_eq!(discriminant_type(num_cases), Int::U32); } } + + #[test] + fn test_find_futures_and_streams() { + let mut resolve = Resolve::default(); + let t0 = resolve.types.alloc(TypeDef { + name: None, + kind: TypeDefKind::Future(Some(Type::U32)), + owner: TypeOwner::None, + docs: Docs::default(), + stability: Stability::Unknown, + }); + let t1 = resolve.types.alloc(TypeDef { + name: None, + kind: TypeDefKind::Future(Some(Type::Id(t0))), + owner: TypeOwner::None, + docs: Docs::default(), + stability: Stability::Unknown, + }); + let t2 = resolve.types.alloc(TypeDef { + name: None, + kind: TypeDefKind::Stream(Type::U32), + owner: TypeOwner::None, + docs: Docs::default(), + stability: Stability::Unknown, + }); + let found = Function { + name: "foo".into(), + kind: FunctionKind::Freestanding, + params: vec![("p1".into(), Type::Id(t1)), ("p2".into(), Type::U32)], + results: Results::Anon(Type::Id(t2)), + docs: Docs::default(), + stability: Stability::Unknown, + } + .find_futures_and_streams(&resolve); + assert_eq!(3, found.len()); + assert_eq!(t0, found[0]); + assert_eq!(t1, found[1]); + assert_eq!(t2, found[2]); + } } diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index e8dfac1994..84e2ebf8b7 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::ErrorContext + | 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 20f6dff989..93a82e3759 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::ErrorContext => false, TypeDefKind::Type(t) => self.all_bits_valid(t), TypeDefKind::Handle(h) => match h { @@ -2990,7 +2991,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)?; @@ -2999,16 +3000,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)?; - } - } + 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 @@ -3399,18 +3392,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::ErrorContext => false, TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index 21885df51e..4c8c18ef44 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::ErrorContext => {} 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..f73dd9e88a 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::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( diff --git a/crates/wit-parser/tests/ui/comments.wit.json b/crates/wit-parser/tests/ui/comments.wit.json index 01e43b15ce..b15b1200f7 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/error-context.wit b/crates/wit-parser/tests/ui/error-context.wit new file mode 100644 index 0000000000..b14a5a3076 --- /dev/null +++ b/crates/wit-parser/tests/ui/error-context.wit @@ -0,0 +1,7 @@ +package foo:error-contexts; + +interface error-contexts { + type t1 = error-context; + + foo: func(x: error-context, y: t1) -> result<_, error-context>; +} diff --git a/crates/wit-parser/tests/ui/error-context.wit.json b/crates/wit-parser/tests/ui/error-context.wit.json new file mode 100644 index 0000000000..1a63f8fb92 --- /dev/null +++ b/crates/wit-parser/tests/ui/error-context.wit.json @@ -0,0 +1,66 @@ +{ + "worlds": [], + "interfaces": [ + { + "name": "error-contexts", + "types": { + "t1": 0 + }, + "functions": { + "foo": { + "name": "foo", + "kind": "freestanding", + "params": [ + { + "name": "x", + "type": 1 + }, + { + "name": "y", + "type": 0 + } + ], + "results": [ + { + "type": 2 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "t1", + "kind": "errorcontext", + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": "errorcontext", + "owner": null + }, + { + "name": null, + "kind": { + "result": { + "ok": null, + "err": 1 + } + }, + "owner": null + } + ], + "packages": [ + { + "name": "foo:error-contexts", + "interfaces": { + "error-contexts": 0 + }, + "worlds": {} + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/streams-and-futures.wit b/crates/wit-parser/tests/ui/streams-and-futures.wit new file mode 100644 index 0000000000..de023fdcf7 --- /dev/null +++ b/crates/wit-parser/tests/ui/streams-and-futures.wit @@ -0,0 +1,14 @@ +package foo:streams-and-futures; + +interface streams-and-futures { + type t1 = stream; + type t2 = stream>; + type t3 = future; + type t4 = future>; + type t5 = option>; + resource r1; + type t6 = stream; + type t7 = future>; + + foo: func(x: stream, y: t6) -> future, string>>; +} diff --git a/crates/wit-parser/tests/ui/streams-and-futures.wit.json b/crates/wit-parser/tests/ui/streams-and-futures.wit.json new file mode 100644 index 0000000000..a422a21a17 --- /dev/null +++ b/crates/wit-parser/tests/ui/streams-and-futures.wit.json @@ -0,0 +1,199 @@ +{ + "worlds": [], + "interfaces": [ + { + "name": "streams-and-futures", + "types": { + "t1": 0, + "t2": 2, + "t3": 3, + "t4": 5, + "t5": 8, + "r1": 9, + "t6": 11, + "t7": 13 + }, + "functions": { + "foo": { + "name": "foo", + "kind": "freestanding", + "params": [ + { + "name": "x", + "type": 14 + }, + { + "name": "y", + "type": 11 + } + ], + "results": [ + { + "type": 17 + } + ] + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "t1", + "kind": { + "stream": "u8" + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "stream": "string" + }, + "owner": null + }, + { + "name": "t2", + "kind": { + "stream": 1 + }, + "owner": { + "interface": 0 + } + }, + { + "name": "t3", + "kind": { + "future": null + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "list": "u8" + }, + "owner": null + }, + { + "name": "t4", + "kind": { + "future": 4 + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "future": null + }, + "owner": null + }, + { + "name": null, + "kind": { + "stream": 6 + }, + "owner": null + }, + { + "name": "t5", + "kind": { + "option": 7 + }, + "owner": { + "interface": 0 + } + }, + { + "name": "r1", + "kind": "resource", + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "handle": { + "own": 9 + } + }, + "owner": null + }, + { + "name": "t6", + "kind": { + "stream": 10 + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "result": { + "ok": 10, + "err": null + } + }, + "owner": null + }, + { + "name": "t7", + "kind": { + "future": 12 + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "stream": "u32" + }, + "owner": null + }, + { + "name": null, + "kind": { + "list": "string" + }, + "owner": null + }, + { + "name": null, + "kind": { + "result": { + "ok": 15, + "err": "string" + } + }, + "owner": null + }, + { + "name": null, + "kind": { + "future": 16 + }, + "owner": null + } + ], + "packages": [ + { + "name": "foo:streams-and-futures", + "interfaces": { + "streams-and-futures": 0 + }, + "worlds": {} + } + ] +} \ No newline at end of file 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 de69c6e1cb..5e0ff82cdc 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 5379956851..b86ffdd0be 100644 --- a/src/bin/wasm-tools/dump.rs +++ b/src/bin/wasm-tools/dump.rs @@ -422,6 +422,31 @@ 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::ErrorContextNew { .. } + | CanonicalFunction::ErrorContextDebugMessage { .. } + | CanonicalFunction::ErrorContextDrop => { + ("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'. diff --git a/tests/local/component-model-async/error-context.wast b/tests/local/component-model-async/error-context.wast new file mode 100644 index 0000000000..3186d0243c --- /dev/null +++ b/tests/local/component-model-async/error-context.wast @@ -0,0 +1,76 @@ +;; error-context.new +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.new" (func $error-context-new (param i32 i32) (result i32))) + ) + (core func $error-context-new (canon error-context.new (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.new" (func $error-context-new)))))) +) + +;; error-context.new; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.new" (func $error-context-new (param i32) (result i32))) + ) + (core func $error-context-new (canon error-context.new (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.new" (func $error-context-new)))))) + ) + "type mismatch for export `error-context.new` of module instantiation argument ``" +) + +;; error-context.debug-message +(component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.debug-message" (func $error-context-debug-message (param i32 i32))) + ) + (core func $error-context-debug-message (canon error-context.debug-message (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.debug-message" (func $error-context-debug-message)))))) +) + +;; error-context.debug-message; incorrect type +(assert_invalid + (component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.debug-message" (func $error-context-debug-message (param i32) (result i32))) + ) + (core func $error-context-debug-message (canon error-context.debug-message (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.debug-message" (func $error-context-debug-message)))))) + ) + "type mismatch for export `error-context.debug-message` of module instantiation argument ``" +) + +;; error-context.drop +(component + (core module $m + (import "" "error-context.drop" (func $error-context-drop (param i32))) + ) + (core func $error-context-drop (canon error-context.drop)) + (core instance $i (instantiate $m (with "" (instance (export "error-context.drop" (func $error-context-drop)))))) +) + +;; error-context.drop; incorrect type +(assert_invalid + (component + (core module $m + (import "" "error-context.drop" (func $error-context-drop (param i32) (result i32))) + ) + (core func $error-context-drop (canon error-context.drop)) + (core instance $i (instantiate $m (with "" (instance (export "error-context.drop" (func $error-context-drop)))))) + ) + "type mismatch for export `error-context.drop` of module instantiation argument ``" +) diff --git a/tests/local/component-model-async/futures.wast b/tests/local/component-model-async/futures.wast new file mode 100644 index 0000000000..89678f253f --- /dev/null +++ b/tests/local/component-model-async/futures.wast @@ -0,0 +1,213 @@ +;; future.new +(component + (core module $m + (import "" "future.new" (func $future-new (result i32))) + ) + (type $future-type (future u8)) + (core func $future-new (canon future.new $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future-new)))))) +) + +;; future.new; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.new" (func $future-new (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-new (canon future.new $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future-new)))))) + ) + "type mismatch for export `future.new` of module instantiation argument ``" +) + +;; future.read +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) +) + +;; future.read; with realloc +(component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future string)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) +) + +;; future.read; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "type mismatch for export `future.read` of module instantiation argument ``" +) + +;; future.read; incorrect type argument +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $string-type string) + (core func $future-read (canon future.read $string-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "`future.read` requires a future type" +) + +;; future.read; missing realloc +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future string)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "canonical option `realloc` is required" +) + +;; future.write +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.write" (func $future-write (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-write (canon future.write $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write)))))) +) + +;; future.write; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.write" (func $future-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-write (canon future.write $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write)))))) + ) + "type mismatch for export `future.write` of module instantiation argument ``" +) + +;; future.cancel-read +(component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) +) + +;; future.cancel-read; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) + ) + "type mismatch for export `future.cancel-read` of module instantiation argument ``" +) + +;; future.cancel-write +(component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) +) + +;; future.cancel-write; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) + ) + "type mismatch for export `future.cancel-write` of module instantiation argument ``" +) + +;; future.close-readable +(component + (core module $m + (import "" "future.close-readable" (func $future-close-readable (param i32))) + ) + (type $future-type (future u8)) + (core func $future-close-readable (canon future.close-readable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.close-readable" (func $future-close-readable)))))) +) + +;; future.close-readable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.close-readable" (func $future-close-readable (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-close-readable (canon future.close-readable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.close-readable" (func $future-close-readable)))))) + ) + "type mismatch for export `future.close-readable` of module instantiation argument ``" +) + +;; future.close-writable +(component + (core module $m + (import "" "future.close-writable" (func $future-close-writable (param i32 i32))) + ) + (type $future-type (future u8)) + (core func $future-close-writable (canon future.close-writable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.close-writable" (func $future-close-writable)))))) +) + +;; future.close-writable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.close-writable" (func $future-close-writable (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-close-writable (canon future.close-writable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.close-writable" (func $future-close-writable)))))) + ) + "type mismatch for export `future.close-writable` of module instantiation argument ``" +) diff --git a/tests/local/component-model-async/lift.wast b/tests/local/component-model-async/lift.wast new file mode 100644 index 0000000000..5db5169c6a --- /dev/null +++ b/tests/local/component-model-async/lift.wast @@ -0,0 +1,87 @@ +;; async lift; no callback +(component + (core module $m + (func (export "foo") (param i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async) + ) +) + +;; async lift; with callback +(component + (core module $m + (func (export "callback") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) +) + +;; async lift; with incorrectly-typed callback +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 f32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "canonical option `callback` uses a core function with an incorrect signature" +) + +;; async lift; with incorrectly-typed core function +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32 i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "lowered parameter types `[I32]` do not match parameter types `[I32, I32]` of core function 0" +) + +;; async lift; with missing callback +(assert_invalid + (component + (core module $m + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "core instance 0 has no export named `callback`" +) + +;; sync lift; with redundant callback +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") (callback (func $i "callback"))) + ) + ) + "cannot specify callback without lifting async" +) diff --git a/tests/local/component-model-async/lower.wast b/tests/local/component-model-async/lower.wast new file mode 100644 index 0000000000..c9806822f4 --- /dev/null +++ b/tests/local/component-model-async/lower.wast @@ -0,0 +1,39 @@ +;; async lower +(component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core func $foo (canon lower (func $foo) async (memory $libc "memory"))) + (core module $m + (func (import "" "foo") (param i32 i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) +) + +;; async lower; with incorrectly-typed core function +(assert_invalid + (component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core func $foo (canon lower (func $foo) async (memory $libc "memory"))) + (core module $m + (func (import "" "foo") (param i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) + ) + "type mismatch for export `foo` of module instantiation argument ``" +) + +;; async lower; missing memory +(assert_invalid + (component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core func $foo (canon lower (func $foo) async)) + (core module $m + (func (import "" "foo") (param i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) + ) + "canonical option `memory` is required" +) diff --git a/tests/local/component-model-async/streams.wast b/tests/local/component-model-async/streams.wast new file mode 100644 index 0000000000..0606c1c93a --- /dev/null +++ b/tests/local/component-model-async/streams.wast @@ -0,0 +1,213 @@ +;; stream.new +(component + (core module $m + (import "" "stream.new" (func $stream-new (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-new (canon stream.new $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream-new)))))) +) + +;; stream.new; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.new" (func $stream-new (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-new (canon stream.new $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream-new)))))) + ) + "type mismatch for export `stream.new` of module instantiation argument ``" +) + +;; stream.read +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) +) + +;; stream.read; with realloc +(component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream string)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) +) + +;; stream.read; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "type mismatch for export `stream.read` of module instantiation argument ``" +) + +;; stream.read; incorrect type argument +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $string-type string) + (core func $stream-read (canon stream.read $string-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "`stream.read` requires a stream type" +) + +;; stream.read; missing realloc +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream string)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "canonical option `realloc` is required" +) + +;; stream.write +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.write" (func $stream-write (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-write (canon stream.write $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write)))))) +) + +;; stream.write; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.write" (func $stream-write (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-write (canon stream.write $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write)))))) + ) + "type mismatch for export `stream.write` of module instantiation argument ``" +) + +;; stream.cancel-read +(component + (core module $m + (import "" "stream.cancel-read" (func $stream-cancel-read (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-read (canon stream.cancel-read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read)))))) +) + +;; stream.cancel-read; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-read" (func $stream-cancel-read (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-read (canon stream.cancel-read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read)))))) + ) + "type mismatch for export `stream.cancel-read` of module instantiation argument ``" +) + +;; stream.cancel-write +(component + (core module $m + (import "" "stream.cancel-write" (func $stream-cancel-write (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-write (canon stream.cancel-write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write)))))) +) + +;; stream.cancel-write; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-write" (func $stream-cancel-write (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-write (canon stream.cancel-write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write)))))) + ) + "type mismatch for export `stream.cancel-write` of module instantiation argument ``" +) + +;; stream.close-readable +(component + (core module $m + (import "" "stream.close-readable" (func $stream-close-readable (param i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-close-readable (canon stream.close-readable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.close-readable" (func $stream-close-readable)))))) +) + +;; stream.close-readable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.close-readable" (func $stream-close-readable (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-close-readable (canon stream.close-readable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.close-readable" (func $stream-close-readable)))))) + ) + "type mismatch for export `stream.close-readable` of module instantiation argument ``" +) + +;; stream.close-writable +(component + (core module $m + (import "" "stream.close-writable" (func $stream-close-writable (param i32 i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-close-writable (canon stream.close-writable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.close-writable" (func $stream-close-writable)))))) +) + +;; stream.close-writable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.close-writable" (func $stream-close-writable (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-close-writable (canon stream.close-writable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.close-writable" (func $stream-close-writable)))))) + ) + "type mismatch for export `stream.close-writable` of module instantiation argument ``" +) diff --git a/tests/local/component-model-async/task-builtins.wast b/tests/local/component-model-async/task-builtins.wast new file mode 100644 index 0000000000..6115b5d699 --- /dev/null +++ b/tests/local/component-model-async/task-builtins.wast @@ -0,0 +1,122 @@ +;; task.backpressure +(component + (core module $m + (import "" "task.backpressure" (func $task-backpressure (param i32))) + ) + (core func $task-backpressure (canon task.backpressure)) + (core instance $i (instantiate $m (with "" (instance (export "task.backpressure" (func $task-backpressure)))))) +) + +;; task.backpressure; incorrect type +(assert_invalid + (component + (core module $m + (import "" "task.backpressure" (func $task-backpressure (param i32 i32))) + ) + (core func $task-backpressure (canon task.backpressure)) + (core instance $i (instantiate $m (with "" (instance (export "task.backpressure" (func $task-backpressure)))))) + ) + "type mismatch for export `task.backpressure` of module instantiation argument ``" +) + +;; task.return +(component + (core module $m + (import "" "task.return" (func $task-return (param i32))) + ) + (core type $task-return-type (func (param i32))) + (core func $task-return (canon task.return $task-return-type)) + (core instance $i (instantiate $m (with "" (instance (export "task.return" (func $task-return)))))) +) + +;; task.wait +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "task.wait" (func $task-wait (param i32) (result i32))) + ) + (core func $task-wait (canon task.wait async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "task.wait" (func $task-wait)))))) +) + +;; task.wait; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "task.wait" (func $task-wait (param i32 i32) (result i32))) + ) + (core func $task-wait (canon task.wait async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "task.wait" (func $task-wait)))))) + ) + "type mismatch for export `task.wait` of module instantiation argument ``" +) + +;; task.poll +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "task.poll" (func $task-poll (param i32) (result i32))) + ) + (core func $task-poll (canon task.poll async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "task.poll" (func $task-poll)))))) +) + +;; task.poll; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "task.poll" (func $task-poll (param i32 i32) (result i32))) + ) + (core func $task-poll (canon task.poll async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "task.poll" (func $task-poll)))))) + ) + "type mismatch for export `task.poll` of module instantiation argument ``" +) + +;; task.yield +(component + (core module $m + (import "" "task.yield" (func $task-yield)) + ) + (core func $task-yield (canon task.yield async)) + (core instance $i (instantiate $m (with "" (instance (export "task.yield" (func $task-yield)))))) +) + +;; task.yield; incorrect type +(assert_invalid + (component + (core module $m + (import "" "task.yield" (func $task-yield (param i32) (result i32))) + ) + (core func $task-yield (canon task.yield async)) + (core instance $i (instantiate $m (with "" (instance (export "task.yield" (func $task-yield)))))) + ) + "type mismatch for export `task.yield` of module instantiation argument ``" +) + +;; subtask.drop +(component + (core module $m + (import "" "subtask.drop" (func $subtask-drop (param i32))) + ) + (core func $subtask-drop (canon subtask.drop)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.drop" (func $subtask-drop)))))) +) + +;; subtask.drop; incorrect type +(assert_invalid + (component + (core module $m + (import "" "subtask.drop" (func $subtask-drop (param i32) (result i32))) + ) + (core func $subtask-drop (canon subtask.drop)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.drop" (func $subtask-drop)))))) + ) + "type mismatch for export `subtask.drop` of module instantiation argument ``" +) diff --git a/tests/local/missing-features/component-model/async.wast b/tests/local/missing-features/component-model/async.wast new file mode 100644 index 0000000000..06c93f9dfd --- /dev/null +++ b/tests/local/missing-features/component-model/async.wast @@ -0,0 +1,339 @@ +;; async lift +(assert_invalid + (component + (core module $m + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async) + ) + ) + "canonical option `async` requires the component model async feature" +) + +;; async lower +(assert_invalid + (component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core func $foo (canon lower (func $foo) async (memory $libc "memory"))) + (core module $m + (func (import "" "foo") (param i32 i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) + ) + "canonical option `async` requires the component model async feature" +) + +;; task.backpressure +(assert_invalid + (component + (core module $m + (import "" "task.backpressure" (func $task-backpressure (param i32))) + ) + (core func $task-backpressure (canon task.backpressure)) + (core instance $i (instantiate $m (with "" (instance (export "task.backpressure" (func $task-backpressure)))))) + ) + "`task.backpressure` requires the component model async feature" +) + +;; task.return +(assert_invalid + (component + (core module $m + (import "" "task.return" (func $task-return (param i32))) + ) + (core type $task-return-type (func (param i32))) + (core func $task-return (canon task.return $task-return-type)) + (core instance $i (instantiate $m (with "" (instance (export "task.return" (func $task-return)))))) + ) + "`task.return` requires the component model async feature" +) + +;; task.wait +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "task.wait" (func $task-wait (param i32) (result i32))) + ) + (core func $task-wait (canon task.wait async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "task.wait" (func $task-wait)))))) + ) + "`task.wait` requires the component model async feature" +) + +;; task.poll +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "task.poll" (func $task-poll (param i32) (result i32))) + ) + (core func $task-poll (canon task.poll async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "task.poll" (func $task-poll)))))) + ) + "`task.poll` requires the component model async feature" +) + +;; task.yield +(assert_invalid + (component + (core module $m + (import "" "task.yield" (func $task-yield)) + ) + (core func $task-yield (canon task.yield async)) + (core instance $i (instantiate $m (with "" (instance (export "task.yield" (func $task-yield)))))) + ) + "`task.yield` requires the component model async feature" +) + +;; subtask.drop +(assert_invalid + (component + (core module $m + (import "" "subtask.drop" (func $subtask-drop (param i32))) + ) + (core func $subtask-drop (canon subtask.drop)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.drop" (func $subtask-drop)))))) + ) + "`subtask.drop` requires the component model async feature" +) + +;; stream.new +(assert_invalid + (component + (core module $m + (import "" "stream.new" (func $stream-new (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-new (canon stream.new $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream-new)))))) + ) + "`stream.new` requires the component model async feature" +) + +;; stream.read +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "`stream.read` requires the component model async feature" +) + +;; stream.write +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.write" (func $stream-write (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-write (canon stream.write $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write)))))) + ) + "`stream.write` requires the component model async feature" +) + +;; stream.cancel-read +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-read" (func $stream-cancel-read (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-read (canon stream.cancel-read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read)))))) + ) + "`stream.cancel-read` requires the component model async feature" +) + +;; stream.cancel-write +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-write" (func $stream-cancel-write (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-write (canon stream.cancel-write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write)))))) + ) + "`stream.cancel-write` requires the component model async feature" +) + +;; stream.close-readable +(assert_invalid + (component + (core module $m + (import "" "stream.close-readable" (func $stream-close-readable (param i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-close-readable (canon stream.close-readable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.close-readable" (func $stream-close-readable)))))) + ) + "`stream.close-readable` requires the component model async feature" +) + +;; stream.close-writable +(assert_invalid + (component + (core module $m + (import "" "stream.close-writable" (func $stream-close-writable (param i32 i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-close-writable (canon stream.close-writable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.close-writable" (func $stream-close-writable)))))) + ) + "`stream.close-writable` requires the component model async feature" +) + +;; future.new +(assert_invalid + (component + (core module $m + (import "" "future.new" (func $future-new (result i32))) + ) + (type $future-type (future u8)) + (core func $future-new (canon future.new $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future-new)))))) + ) + "`future.new` requires the component model async feature" +) + +;; future.read +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "`future.read` requires the component model async feature" +) + +;; future.write +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.write" (func $future-write (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-write (canon future.write $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write)))))) + ) + "`future.write` requires the component model async feature" +) + +;; future.cancel-read +(assert_invalid + (component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) + ) + "`future.cancel-read` requires the component model async feature" +) + +;; future.cancel-write +(assert_invalid + (component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) + ) + "`future.cancel-write` requires the component model async feature" +) + +;; future.close-readable +(assert_invalid + (component + (core module $m + (import "" "future.close-readable" (func $future-close-readable (param i32))) + ) + (type $future-type (future u8)) + (core func $future-close-readable (canon future.close-readable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.close-readable" (func $future-close-readable)))))) + ) + "`future.close-readable` requires the component model async feature" +) + +;; future.close-writable +(assert_invalid + (component + (core module $m + (import "" "future.close-writable" (func $future-close-writable (param i32 i32))) + ) + (type $future-type (future u8)) + (core func $future-close-writable (canon future.close-writable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.close-writable" (func $future-close-writable)))))) + ) + "`future.close-writable` requires the component model async feature" +) + +;; error-context.new +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.new" (func $error-context-new (param i32 i32) (result i32))) + ) + (core func $error-context-new (canon error-context.new (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.new" (func $error-context-new)))))) + ) + "`error-context.new` requires the component model async feature" +) + +;; error-context.debug-message +(assert_invalid + (component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.debug-message" (func $error-context-debug-message (param i32 i32))) + ) + (core func $error-context-debug-message (canon error-context.debug-message (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.debug-message" (func $error-context-debug-message)))))) + ) + "`error-context.debug-message` requires the component model async feature" +) + +;; error-context.drop +(assert_invalid + (component + (core module $m + (import "" "error-context.drop" (func $error-context-drop (param i32))) + ) + (core func $error-context-drop (canon error-context.drop)) + (core instance $i (instantiate $m (with "" (instance (export "error-context.drop" (func $error-context-drop)))))) + ) + "`error-context.drop` requires the component model async feature" +) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 7a03a3d156..d1478d103e 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -684,6 +684,10 @@ impl TestState { "wasm-3.0" => { features.insert(WasmFeatures::WASM3); } + "component-model-async" => { + features.insert(WasmFeatures::COMPONENT_MODEL); + features.insert(WasmFeatures::COMPONENT_MODEL_ASYNC); + } _ => {} } } diff --git a/tests/snapshots/local/component-model-async/error-context.wast.json b/tests/snapshots/local/component-model-async/error-context.wast.json new file mode 100644 index 0000000000..527c3a2dc4 --- /dev/null +++ b/tests/snapshots/local/component-model-async/error-context.wast.json @@ -0,0 +1,44 @@ +{ + "source_filename": "tests/local/component-model-async/error-context.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "error-context.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 14, + "filename": "error-context.1.wasm", + "module_type": "binary", + "text": "type mismatch for export `error-context.new` of module instantiation argument ``" + }, + { + "type": "module", + "line": 27, + "filename": "error-context.2.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 42, + "filename": "error-context.3.wasm", + "module_type": "binary", + "text": "type mismatch for export `error-context.debug-message` of module instantiation argument ``" + }, + { + "type": "module", + "line": 58, + "filename": "error-context.4.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 68, + "filename": "error-context.5.wasm", + "module_type": "binary", + "text": "type mismatch for export `error-context.drop` of module instantiation argument ``" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/component-model-async/error-context.wast/0.print b/tests/snapshots/local/component-model-async/error-context.wast/0.print new file mode 100644 index 0000000000..78a2928dde --- /dev/null +++ b/tests/snapshots/local/component-model-async/error-context.wast/0.print @@ -0,0 +1,20 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (import "" "error-context.new" (func $error-context-new (;0;) (type 0))) + ) + (alias core export $libc "memory" (core memory (;0;))) + (core func $error-context-new (;0;) (canon error-context.new (memory 0))) + (core instance (;1;) + (export "error-context.new" (func $error-context-new)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/error-context.wast/2.print b/tests/snapshots/local/component-model-async/error-context.wast/2.print new file mode 100644 index 0000000000..43517a6d18 --- /dev/null +++ b/tests/snapshots/local/component-model-async/error-context.wast/2.print @@ -0,0 +1,26 @@ +(component + (core module $libc (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (memory (;0;) 1) + (export "realloc" (func 0)) + (export "memory" (memory 0)) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32))) + (import "" "error-context.debug-message" (func $error-context-debug-message (;0;) (type 0))) + ) + (alias core export $libc "memory" (core memory (;0;))) + (alias core export $libc "realloc" (core func (;0;))) + (core func $error-context-debug-message (;1;) (canon error-context.debug-message (memory 0) (realloc 0))) + (core instance (;1;) + (export "error-context.debug-message" (func $error-context-debug-message)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/error-context.wast/4.print b/tests/snapshots/local/component-model-async/error-context.wast/4.print new file mode 100644 index 0000000000..3bf0231bb2 --- /dev/null +++ b/tests/snapshots/local/component-model-async/error-context.wast/4.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (import "" "error-context.drop" (func $error-context-drop (;0;) (type 0))) + ) + (core func $error-context-drop (;0;) (canon error-context.drop)) + (core instance (;0;) + (export "error-context.drop" (func $error-context-drop)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast.json b/tests/snapshots/local/component-model-async/futures.wast.json new file mode 100644 index 0000000000..3d9db1abfe --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast.json @@ -0,0 +1,116 @@ +{ + "source_filename": "tests/local/component-model-async/futures.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "futures.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 13, + "filename": "futures.1.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.new` of module instantiation argument ``" + }, + { + "type": "module", + "line": 25, + "filename": "futures.2.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 37, + "filename": "futures.3.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 53, + "filename": "futures.4.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.read` of module instantiation argument ``" + }, + { + "type": "assert_invalid", + "line": 68, + "filename": "futures.5.wasm", + "module_type": "binary", + "text": "`future.read` requires a future type" + }, + { + "type": "assert_invalid", + "line": 83, + "filename": "futures.6.wasm", + "module_type": "binary", + "text": "canonical option `realloc` is required" + }, + { + "type": "module", + "line": 97, + "filename": "futures.7.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 110, + "filename": "futures.8.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.write` of module instantiation argument ``" + }, + { + "type": "module", + "line": 124, + "filename": "futures.9.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 135, + "filename": "futures.10.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.cancel-read` of module instantiation argument ``" + }, + { + "type": "module", + "line": 147, + "filename": "futures.11.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 158, + "filename": "futures.12.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.cancel-write` of module instantiation argument ``" + }, + { + "type": "module", + "line": 170, + "filename": "futures.13.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 181, + "filename": "futures.14.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.close-readable` of module instantiation argument ``" + }, + { + "type": "module", + "line": 193, + "filename": "futures.15.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 204, + "filename": "futures.16.wasm", + "module_type": "binary", + "text": "type mismatch for export `future.close-writable` of module instantiation argument ``" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/component-model-async/futures.wast/0.print b/tests/snapshots/local/component-model-async/futures.wast/0.print new file mode 100644 index 0000000000..ad34b0c48e --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/0.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (result i32))) + (import "" "future.new" (func $future-new (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (core func $future-new (;0;) (canon future.new $future-type)) + (core instance (;0;) + (export "future.new" (func $future-new)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/11.print b/tests/snapshots/local/component-model-async/futures.wast/11.print new file mode 100644 index 0000000000..6e15e579bb --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/11.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32) (result i32))) + (import "" "future.cancel-write" (func $future-cancel-write (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (core func $future-cancel-write (;0;) (canon future.cancel-write $future-type async)) + (core instance (;0;) + (export "future.cancel-write" (func $future-cancel-write)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/13.print b/tests/snapshots/local/component-model-async/futures.wast/13.print new file mode 100644 index 0000000000..7754683368 --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/13.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (import "" "future.close-readable" (func $future-close-readable (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (core func $future-close-readable (;0;) (canon future.close-readable $future-type)) + (core instance (;0;) + (export "future.close-readable" (func $future-close-readable)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/15.print b/tests/snapshots/local/component-model-async/futures.wast/15.print new file mode 100644 index 0000000000..c1242cd6eb --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/15.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32 i32))) + (import "" "future.close-writable" (func $future-close-writable (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (core func $future-close-writable (;0;) (canon future.close-writable $future-type)) + (core instance (;0;) + (export "future.close-writable" (func $future-close-writable)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/2.print b/tests/snapshots/local/component-model-async/futures.wast/2.print new file mode 100644 index 0000000000..8fbf77939c --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/2.print @@ -0,0 +1,21 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (import "" "future.read" (func $future-read (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (alias core export $libc "memory" (core memory (;0;))) + (core func $future-read (;0;) (canon future.read $future-type async (memory 0))) + (core instance (;1;) + (export "future.read" (func $future-read)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/3.print b/tests/snapshots/local/component-model-async/futures.wast/3.print new file mode 100644 index 0000000000..d08dbedf8e --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/3.print @@ -0,0 +1,27 @@ +(component + (core module $libc (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (memory (;0;) 1) + (export "realloc" (func 0)) + (export "memory" (memory 0)) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (import "" "future.read" (func $future-read (;0;) (type 0))) + ) + (type $future-type (;0;) (future string)) + (alias core export $libc "memory" (core memory (;0;))) + (alias core export $libc "realloc" (core func (;0;))) + (core func $future-read (;1;) (canon future.read $future-type async (memory 0) (realloc 0))) + (core instance (;1;) + (export "future.read" (func $future-read)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/7.print b/tests/snapshots/local/component-model-async/futures.wast/7.print new file mode 100644 index 0000000000..a9ec39f18f --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/7.print @@ -0,0 +1,21 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (import "" "future.write" (func $future-write (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (alias core export $libc "memory" (core memory (;0;))) + (core func $future-write (;0;) (canon future.write $future-type async (memory 0))) + (core instance (;1;) + (export "future.write" (func $future-write)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/futures.wast/9.print b/tests/snapshots/local/component-model-async/futures.wast/9.print new file mode 100644 index 0000000000..443e958b83 --- /dev/null +++ b/tests/snapshots/local/component-model-async/futures.wast/9.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32) (result i32))) + (import "" "future.cancel-read" (func $future-cancel-read (;0;) (type 0))) + ) + (type $future-type (;0;) (future u8)) + (core func $future-cancel-read (;0;) (canon future.cancel-read $future-type async)) + (core instance (;0;) + (export "future.cancel-read" (func $future-cancel-read)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/lift.wast.json b/tests/snapshots/local/component-model-async/lift.wast.json new file mode 100644 index 0000000000..9fe389fc3a --- /dev/null +++ b/tests/snapshots/local/component-model-async/lift.wast.json @@ -0,0 +1,45 @@ +{ + "source_filename": "tests/local/component-model-async/lift.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "lift.0.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 14, + "filename": "lift.1.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 28, + "filename": "lift.2.wasm", + "module_type": "binary", + "text": "canonical option `callback` uses a core function with an incorrect signature" + }, + { + "type": "assert_invalid", + "line": 44, + "filename": "lift.3.wasm", + "module_type": "binary", + "text": "lowered parameter types `[I32]` do not match parameter types `[I32, I32]` of core function 0" + }, + { + "type": "assert_invalid", + "line": 60, + "filename": "lift.4.wasm", + "module_type": "binary", + "text": "core instance 0 has no export named `callback`" + }, + { + "type": "assert_invalid", + "line": 75, + "filename": "lift.5.wasm", + "module_type": "binary", + "text": "cannot specify callback without lifting async" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/component-model-async/lift.wast/0.print b/tests/snapshots/local/component-model-async/lift.wast/0.print new file mode 100644 index 0000000000..854ce4d60f --- /dev/null +++ b/tests/snapshots/local/component-model-async/lift.wast/0.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (export "foo" (func 0)) + (func (;0;) (type 0) (param i32) + unreachable + ) + ) + (core instance $i (;0;) (instantiate $m)) + (type (;0;) (func (param "p1" u32) (result u32))) + (alias core export $i "foo" (core func (;0;))) + (func (;0;) (type 0) (canon lift (core func 0) async)) + (export (;1;) "foo" (func 0)) +) diff --git a/tests/snapshots/local/component-model-async/lift.wast/1.print b/tests/snapshots/local/component-model-async/lift.wast/1.print new file mode 100644 index 0000000000..c56b178979 --- /dev/null +++ b/tests/snapshots/local/component-model-async/lift.wast/1.print @@ -0,0 +1,20 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (type (;1;) (func (param i32) (result i32))) + (export "callback" (func 0)) + (export "foo" (func 1)) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;1;) (type 1) (param i32) (result i32) + unreachable + ) + ) + (core instance $i (;0;) (instantiate $m)) + (type (;0;) (func (param "p1" u32) (result u32))) + (alias core export $i "foo" (core func (;0;))) + (alias core export $i "callback" (core func (;1;))) + (func (;0;) (type 0) (canon lift (core func 0) async (callback 1))) + (export (;1;) "foo" (func 0)) +) diff --git a/tests/snapshots/local/component-model-async/lower.wast.json b/tests/snapshots/local/component-model-async/lower.wast.json new file mode 100644 index 0000000000..7da59f8d9d --- /dev/null +++ b/tests/snapshots/local/component-model-async/lower.wast.json @@ -0,0 +1,25 @@ +{ + "source_filename": "tests/local/component-model-async/lower.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "lower.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 15, + "filename": "lower.1.wasm", + "module_type": "binary", + "text": "type mismatch for export `foo` of module instantiation argument ``" + }, + { + "type": "assert_invalid", + "line": 30, + "filename": "lower.2.wasm", + "module_type": "binary", + "text": "canonical option `memory` is required" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/component-model-async/lower.wast/0.print b/tests/snapshots/local/component-model-async/lower.wast/0.print new file mode 100644 index 0000000000..fee9919556 --- /dev/null +++ b/tests/snapshots/local/component-model-async/lower.wast/0.print @@ -0,0 +1,22 @@ +(component + (type (;0;) (func (param "p1" u32) (result u32))) + (import "foo" (func $foo (;0;) (type 0))) + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (alias core export $libc "memory" (core memory (;0;))) + (core func $foo (;0;) (canon lower (func $foo) async (memory 0))) + (core module $m (;1;) + (type (;0;) (func (param i32 i32) (result i32))) + (import "" "foo" (func (;0;) (type 0))) + ) + (core instance (;1;) + (export "foo" (func $foo)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast.json b/tests/snapshots/local/component-model-async/streams.wast.json new file mode 100644 index 0000000000..e94937d5c5 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast.json @@ -0,0 +1,116 @@ +{ + "source_filename": "tests/local/component-model-async/streams.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "streams.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 13, + "filename": "streams.1.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.new` of module instantiation argument ``" + }, + { + "type": "module", + "line": 25, + "filename": "streams.2.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 37, + "filename": "streams.3.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 53, + "filename": "streams.4.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.read` of module instantiation argument ``" + }, + { + "type": "assert_invalid", + "line": 68, + "filename": "streams.5.wasm", + "module_type": "binary", + "text": "`stream.read` requires a stream type" + }, + { + "type": "assert_invalid", + "line": 83, + "filename": "streams.6.wasm", + "module_type": "binary", + "text": "canonical option `realloc` is required" + }, + { + "type": "module", + "line": 97, + "filename": "streams.7.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 110, + "filename": "streams.8.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.write` of module instantiation argument ``" + }, + { + "type": "module", + "line": 124, + "filename": "streams.9.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 135, + "filename": "streams.10.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.cancel-read` of module instantiation argument ``" + }, + { + "type": "module", + "line": 147, + "filename": "streams.11.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 158, + "filename": "streams.12.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.cancel-write` of module instantiation argument ``" + }, + { + "type": "module", + "line": 170, + "filename": "streams.13.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 181, + "filename": "streams.14.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.close-readable` of module instantiation argument ``" + }, + { + "type": "module", + "line": 193, + "filename": "streams.15.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 204, + "filename": "streams.16.wasm", + "module_type": "binary", + "text": "type mismatch for export `stream.close-writable` of module instantiation argument ``" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/component-model-async/streams.wast/0.print b/tests/snapshots/local/component-model-async/streams.wast/0.print new file mode 100644 index 0000000000..6fc0f53fb8 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/0.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (result i32))) + (import "" "stream.new" (func $stream-new (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (core func $stream-new (;0;) (canon stream.new $stream-type)) + (core instance (;0;) + (export "stream.new" (func $stream-new)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/11.print b/tests/snapshots/local/component-model-async/streams.wast/11.print new file mode 100644 index 0000000000..62499dfa3e --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/11.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32) (result i32))) + (import "" "stream.cancel-write" (func $stream-cancel-write (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (core func $stream-cancel-write (;0;) (canon stream.cancel-write $stream-type async)) + (core instance (;0;) + (export "stream.cancel-write" (func $stream-cancel-write)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/13.print b/tests/snapshots/local/component-model-async/streams.wast/13.print new file mode 100644 index 0000000000..55c45e4b9d --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/13.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (import "" "stream.close-readable" (func $stream-close-readable (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (core func $stream-close-readable (;0;) (canon stream.close-readable $stream-type)) + (core instance (;0;) + (export "stream.close-readable" (func $stream-close-readable)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/15.print b/tests/snapshots/local/component-model-async/streams.wast/15.print new file mode 100644 index 0000000000..d58f6ec2c1 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/15.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32 i32))) + (import "" "stream.close-writable" (func $stream-close-writable (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (core func $stream-close-writable (;0;) (canon stream.close-writable $stream-type)) + (core instance (;0;) + (export "stream.close-writable" (func $stream-close-writable)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/2.print b/tests/snapshots/local/component-model-async/streams.wast/2.print new file mode 100644 index 0000000000..64815fd4c3 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/2.print @@ -0,0 +1,21 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32 i32) (result i32))) + (import "" "stream.read" (func $stream-read (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (alias core export $libc "memory" (core memory (;0;))) + (core func $stream-read (;0;) (canon stream.read $stream-type async (memory 0))) + (core instance (;1;) + (export "stream.read" (func $stream-read)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/3.print b/tests/snapshots/local/component-model-async/streams.wast/3.print new file mode 100644 index 0000000000..5a46ea8626 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/3.print @@ -0,0 +1,27 @@ +(component + (core module $libc (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (memory (;0;) 1) + (export "realloc" (func 0)) + (export "memory" (memory 0)) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32 i32) (result i32))) + (import "" "stream.read" (func $stream-read (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream string)) + (alias core export $libc "memory" (core memory (;0;))) + (alias core export $libc "realloc" (core func (;0;))) + (core func $stream-read (;1;) (canon stream.read $stream-type async (memory 0) (realloc 0))) + (core instance (;1;) + (export "stream.read" (func $stream-read)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/7.print b/tests/snapshots/local/component-model-async/streams.wast/7.print new file mode 100644 index 0000000000..cb4f2f8c23 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/7.print @@ -0,0 +1,21 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32 i32 i32) (result i32))) + (import "" "stream.write" (func $stream-write (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (alias core export $libc "memory" (core memory (;0;))) + (core func $stream-write (;0;) (canon stream.write $stream-type async (memory 0))) + (core instance (;1;) + (export "stream.write" (func $stream-write)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/streams.wast/9.print b/tests/snapshots/local/component-model-async/streams.wast/9.print new file mode 100644 index 0000000000..721b56bc92 --- /dev/null +++ b/tests/snapshots/local/component-model-async/streams.wast/9.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32) (result i32))) + (import "" "stream.cancel-read" (func $stream-cancel-read (;0;) (type 0))) + ) + (type $stream-type (;0;) (stream u8)) + (core func $stream-cancel-read (;0;) (canon stream.cancel-read $stream-type async)) + (core instance (;0;) + (export "stream.cancel-read" (func $stream-cancel-read)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast.json b/tests/snapshots/local/component-model-async/task-builtins.wast.json new file mode 100644 index 0000000000..abc6d5cb0e --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast.json @@ -0,0 +1,76 @@ +{ + "source_filename": "tests/local/component-model-async/task-builtins.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "task-builtins.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 12, + "filename": "task-builtins.1.wasm", + "module_type": "binary", + "text": "type mismatch for export `task.backpressure` of module instantiation argument ``" + }, + { + "type": "module", + "line": 23, + "filename": "task-builtins.2.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 33, + "filename": "task-builtins.3.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 45, + "filename": "task-builtins.4.wasm", + "module_type": "binary", + "text": "type mismatch for export `task.wait` of module instantiation argument ``" + }, + { + "type": "module", + "line": 58, + "filename": "task-builtins.5.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 70, + "filename": "task-builtins.6.wasm", + "module_type": "binary", + "text": "type mismatch for export `task.poll` of module instantiation argument ``" + }, + { + "type": "module", + "line": 83, + "filename": "task-builtins.7.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 93, + "filename": "task-builtins.8.wasm", + "module_type": "binary", + "text": "type mismatch for export `task.yield` of module instantiation argument ``" + }, + { + "type": "module", + "line": 104, + "filename": "task-builtins.9.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 114, + "filename": "task-builtins.10.wasm", + "module_type": "binary", + "text": "type mismatch for export `subtask.drop` of module instantiation argument ``" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/0.print b/tests/snapshots/local/component-model-async/task-builtins.wast/0.print new file mode 100644 index 0000000000..20c01cd721 --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast/0.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (import "" "task.backpressure" (func $task-backpressure (;0;) (type 0))) + ) + (core func $task-backpressure (;0;) (canon task.backpressure)) + (core instance (;0;) + (export "task.backpressure" (func $task-backpressure)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/2.print b/tests/snapshots/local/component-model-async/task-builtins.wast/2.print new file mode 100644 index 0000000000..0209f7a72a --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast/2.print @@ -0,0 +1,15 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (import "" "task.return" (func $task-return (;0;) (type 0))) + ) + (core type $task-return-type (;0;) (func (param i32))) + (core func $task-return (;0;) (canon task.return 0)) + (core instance (;0;) + (export "task.return" (func $task-return)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/3.print b/tests/snapshots/local/component-model-async/task-builtins.wast/3.print new file mode 100644 index 0000000000..05c4076330 --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast/3.print @@ -0,0 +1,20 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32) (result i32))) + (import "" "task.wait" (func $task-wait (;0;) (type 0))) + ) + (alias core export $libc "memory" (core memory (;0;))) + (core func $task-wait (;0;) (canon task.wait async (memory 0))) + (core instance (;1;) + (export "task.wait" (func $task-wait)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/5.print b/tests/snapshots/local/component-model-async/task-builtins.wast/5.print new file mode 100644 index 0000000000..05bfe3aa76 --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast/5.print @@ -0,0 +1,20 @@ +(component + (core module $libc (;0;) + (memory (;0;) 1) + (export "memory" (memory 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (core module $m (;1;) + (type (;0;) (func (param i32) (result i32))) + (import "" "task.poll" (func $task-poll (;0;) (type 0))) + ) + (alias core export $libc "memory" (core memory (;0;))) + (core func $task-poll (;0;) (canon task.poll async (memory 0))) + (core instance (;1;) + (export "task.poll" (func $task-poll)) + ) + (core instance $i (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/7.print b/tests/snapshots/local/component-model-async/task-builtins.wast/7.print new file mode 100644 index 0000000000..0851c2de88 --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast/7.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func)) + (import "" "task.yield" (func $task-yield (;0;) (type 0))) + ) + (core func $task-yield (;0;) (canon task.yield async)) + (core instance (;0;) + (export "task.yield" (func $task-yield)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/9.print b/tests/snapshots/local/component-model-async/task-builtins.wast/9.print new file mode 100644 index 0000000000..038cc25c37 --- /dev/null +++ b/tests/snapshots/local/component-model-async/task-builtins.wast/9.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32))) + (import "" "subtask.drop" (func $subtask-drop (;0;) (type 0))) + ) + (core func $subtask-drop (;0;) (canon subtask.drop)) + (core instance (;0;) + (export "subtask.drop" (func $subtask-drop)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/local/missing-features/component-model/async.wast.json b/tests/snapshots/local/missing-features/component-model/async.wast.json new file mode 100644 index 0000000000..6aabbc4f37 --- /dev/null +++ b/tests/snapshots/local/missing-features/component-model/async.wast.json @@ -0,0 +1,180 @@ +{ + "source_filename": "tests/local/missing-features/component-model/async.wast", + "commands": [ + { + "type": "assert_invalid", + "line": 3, + "filename": "async.0.wasm", + "module_type": "binary", + "text": "canonical option `async` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 18, + "filename": "async.1.wasm", + "module_type": "binary", + "text": "canonical option `async` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 33, + "filename": "async.2.wasm", + "module_type": "binary", + "text": "`task.backpressure` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 45, + "filename": "async.3.wasm", + "module_type": "binary", + "text": "`task.return` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 58, + "filename": "async.4.wasm", + "module_type": "binary", + "text": "`task.wait` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 72, + "filename": "async.5.wasm", + "module_type": "binary", + "text": "`task.poll` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 86, + "filename": "async.6.wasm", + "module_type": "binary", + "text": "`task.yield` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 98, + "filename": "async.7.wasm", + "module_type": "binary", + "text": "`subtask.drop` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 110, + "filename": "async.8.wasm", + "module_type": "binary", + "text": "`stream.new` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 123, + "filename": "async.9.wasm", + "module_type": "binary", + "text": "`stream.read` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 138, + "filename": "async.10.wasm", + "module_type": "binary", + "text": "`stream.write` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 153, + "filename": "async.11.wasm", + "module_type": "binary", + "text": "`stream.cancel-read` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 166, + "filename": "async.12.wasm", + "module_type": "binary", + "text": "`stream.cancel-write` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 179, + "filename": "async.13.wasm", + "module_type": "binary", + "text": "`stream.close-readable` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 192, + "filename": "async.14.wasm", + "module_type": "binary", + "text": "`stream.close-writable` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 205, + "filename": "async.15.wasm", + "module_type": "binary", + "text": "`future.new` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 218, + "filename": "async.16.wasm", + "module_type": "binary", + "text": "`future.read` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 233, + "filename": "async.17.wasm", + "module_type": "binary", + "text": "`future.write` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 248, + "filename": "async.18.wasm", + "module_type": "binary", + "text": "`future.cancel-read` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 261, + "filename": "async.19.wasm", + "module_type": "binary", + "text": "`future.cancel-write` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 274, + "filename": "async.20.wasm", + "module_type": "binary", + "text": "`future.close-readable` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 287, + "filename": "async.21.wasm", + "module_type": "binary", + "text": "`future.close-writable` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 300, + "filename": "async.22.wasm", + "module_type": "binary", + "text": "`error-context.new` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 314, + "filename": "async.23.wasm", + "module_type": "binary", + "text": "`error-context.debug-message` requires the component model async feature" + }, + { + "type": "assert_invalid", + "line": 331, + "filename": "async.24.wasm", + "module_type": "binary", + "text": "`error-context.drop` requires the component model async feature" + } + ] +} \ No newline at end of file