diff --git a/crates/wasm-encoder/src/component/builder.rs b/crates/wasm-encoder/src/component/builder.rs index d660ae55a7..0413fac639 100644 --- a/crates/wasm-encoder/src/component/builder.rs +++ b/crates/wasm-encoder/src/component/builder.rs @@ -425,9 +425,9 @@ impl ComponentBuilder { inc(&mut self.core_funcs) } - /// Declares a new `task.yield` intrinsic. - pub fn yield_(&mut self, async_: bool) -> u32 { - self.canonical_functions().yield_(async_); + /// Declares a new `thread.yield` intrinsic. + pub fn thread_yield(&mut self, cancellable: bool) -> u32 { + self.canonical_functions().thread_yield(cancellable); inc(&mut self.core_funcs) } @@ -577,14 +577,16 @@ impl ComponentBuilder { } /// Declares a new `waitable-set.wait` intrinsic. - pub fn waitable_set_wait(&mut self, async_: bool, memory: u32) -> u32 { - self.canonical_functions().waitable_set_wait(async_, memory); + pub fn waitable_set_wait(&mut self, cancellable: bool, memory: u32) -> u32 { + self.canonical_functions() + .waitable_set_wait(cancellable, memory); inc(&mut self.core_funcs) } /// Declares a new `waitable-set.poll` intrinsic. - pub fn waitable_set_poll(&mut self, async_: bool, memory: u32) -> u32 { - self.canonical_functions().waitable_set_poll(async_, memory); + pub fn waitable_set_poll(&mut self, cancellable: bool, memory: u32) -> u32 { + self.canonical_functions() + .waitable_set_poll(cancellable, memory); inc(&mut self.core_funcs) } @@ -600,6 +602,43 @@ impl ComponentBuilder { inc(&mut self.core_funcs) } + /// Declares a new `thread.index` intrinsic. + pub fn thread_index(&mut self) -> u32 { + self.canonical_functions().thread_index(); + inc(&mut self.core_funcs) + } + + /// Declares a new `thread.new_indirect` intrinsic. + pub fn thread_new_indirect(&mut self, func_ty_idx: u32, table_index: u32) -> u32 { + self.canonical_functions() + .thread_new_indirect(func_ty_idx, table_index); + inc(&mut self.core_funcs) + } + + /// Declares a new `thread.switch-to` intrinsic. + pub fn thread_switch_to(&mut self, cancellable: bool) -> u32 { + self.canonical_functions().thread_switch_to(cancellable); + inc(&mut self.core_funcs) + } + + /// Declares a new `thread.suspend` intrinsic. + pub fn thread_suspend(&mut self, cancellable: bool) -> u32 { + self.canonical_functions().thread_suspend(cancellable); + inc(&mut self.core_funcs) + } + + /// Declares a new `thread.resume-later` intrinsic. + pub fn thread_resume_later(&mut self) -> u32 { + self.canonical_functions().thread_resume_later(); + inc(&mut self.core_funcs) + } + + /// Declares a new `thread.yield-to` intrinsic. + pub fn thread_yield_to(&mut self, cancellable: bool) -> u32 { + self.canonical_functions().thread_yield_to(cancellable); + 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 8724a1dd49..93310c28dc 100644 --- a/crates/wasm-encoder/src/component/canonicals.rs +++ b/crates/wasm-encoder/src/component/canonicals.rs @@ -249,10 +249,10 @@ impl CanonicalFunctionSection { /// 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 yield_(&mut self, async_: bool) -> &mut Self { + /// If `cancellable` is true, the caller instance may be reentered. + pub fn thread_yield(&mut self, cancellable: bool) -> &mut Self { self.bytes.push(0x0c); - self.bytes.push(if async_ { 1 } else { 0 }); + self.bytes.push(if cancellable { 1 } else { 0 }); self.num_added += 1; self } @@ -498,6 +498,59 @@ impl CanonicalFunctionSection { self } + /// Declare a new `thread.index` intrinsic, used to get the index of the + /// current thread. + pub fn thread_index(&mut self) -> &mut Self { + self.bytes.push(0x26); + self.num_added += 1; + self + } + + /// Declare a new `thread.new_indirect` intrinsic, used to create a new + /// thread by invoking a function indirectly through a `funcref` table. + pub fn thread_new_indirect(&mut self, ty_index: u32, table_index: u32) -> &mut Self { + self.bytes.push(0x27); + ty_index.encode(&mut self.bytes); + table_index.encode(&mut self.bytes); + self.num_added += 1; + self + } + + /// Declare a new `thread.switch-to` intrinsic, used to switch execution to + /// another thread. + pub fn thread_switch_to(&mut self, cancellable: bool) -> &mut Self { + self.bytes.push(0x28); + self.bytes.push(if cancellable { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Declare a new `thread.suspend` intrinsic, used to suspend execution of + /// the current thread. + pub fn thread_suspend(&mut self, cancellable: bool) -> &mut Self { + self.bytes.push(0x29); + self.bytes.push(if cancellable { 1 } else { 0 }); + self.num_added += 1; + self + } + + /// Declare a new `thread.resume-later` intrinsic, used to resume execution + /// of the given thread. + pub fn thread_resume_later(&mut self) -> &mut Self { + self.bytes.push(0x2a); + self.num_added += 1; + self + } + + /// Declare a new `thread.yield-to` intrinsic, used to yield execution to + /// a given thread. + pub fn thread_yield_to(&mut self, cancellable: bool) -> &mut Self { + self.bytes.push(0x2b); + self.bytes.push(if cancellable { 1 } else { 0 }); + self.num_added += 1; + self + } + fn encode_options(&mut self, options: O) -> &mut Self where O: IntoIterator, diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index 9f53eaed97..417c5a99e9 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -995,8 +995,8 @@ pub mod component_utils { wasmparser::CanonicalFunction::ContextSet(i) => { section.context_set(i); } - wasmparser::CanonicalFunction::Yield { async_ } => { - section.yield_(async_); + wasmparser::CanonicalFunction::ThreadYield { cancellable } => { + section.thread_yield(cancellable); } wasmparser::CanonicalFunction::SubtaskDrop => { section.subtask_drop(); @@ -1082,11 +1082,17 @@ pub mod component_utils { wasmparser::CanonicalFunction::WaitableSetNew => { section.waitable_set_new(); } - wasmparser::CanonicalFunction::WaitableSetWait { async_, memory } => { - section.waitable_set_wait(async_, reencoder.memory_index(memory)?); + wasmparser::CanonicalFunction::WaitableSetWait { + cancellable, + memory, + } => { + section.waitable_set_wait(cancellable, reencoder.memory_index(memory)?); } - wasmparser::CanonicalFunction::WaitableSetPoll { async_, memory } => { - section.waitable_set_poll(async_, reencoder.memory_index(memory)?); + wasmparser::CanonicalFunction::WaitableSetPoll { + cancellable, + memory, + } => { + section.waitable_set_poll(cancellable, reencoder.memory_index(memory)?); } wasmparser::CanonicalFunction::WaitableSetDrop => { section.waitable_set_drop(); @@ -1094,6 +1100,29 @@ pub mod component_utils { wasmparser::CanonicalFunction::WaitableJoin => { section.waitable_join(); } + wasmparser::CanonicalFunction::ThreadIndex => { + section.thread_index(); + } + wasmparser::CanonicalFunction::ThreadNewIndirect { + func_ty_index, + table_index, + } => { + let func_ty = reencoder.type_index(func_ty_index)?; + let table_index = reencoder.table_index(table_index)?; + section.thread_new_indirect(func_ty, table_index); + } + wasmparser::CanonicalFunction::ThreadSwitchTo { cancellable } => { + section.thread_switch_to(cancellable); + } + wasmparser::CanonicalFunction::ThreadSuspend { cancellable } => { + section.thread_suspend(cancellable); + } + wasmparser::CanonicalFunction::ThreadResumeLater => { + section.thread_resume_later(); + } + wasmparser::CanonicalFunction::ThreadYieldTo { cancellable } => { + section.thread_yield_to(cancellable); + } } Ok(()) } diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index 2ef63ac5a5..6072bb44c0 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -265,22 +265,27 @@ define_wasm_features! { /// Corresponds to the ๐Ÿš character in /// . pub cm_async_builtins: CM_ASYNC_BUILTINS(1 << 29) = false; + /// Support for threading in the component model proposal. + /// + /// Corresponds to the ๐Ÿงต character in + /// . + pub cm_threading: CM_THREADING(1 << 30) = false; /// Gates some intrinsics being marked with `error-context` in the component /// model async proposal. /// /// Corresponds to the ๐Ÿ“ character in /// . - pub cm_error_context: CM_ERROR_CONTEXT(1 << 30) = false; + pub cm_error_context: CM_ERROR_CONTEXT(1 << 31) = false; /// Support for fixed size lists /// /// Corresponds to the ๐Ÿ”ง character in /// . - pub cm_fixed_size_list: CM_FIXED_SIZE_LIST(1 << 31) = false; + pub cm_fixed_size_list: CM_FIXED_SIZE_LIST(1 << 32) = false; /// Support for Wasm GC in the component model proposal. /// /// Corresponds to the ๐Ÿ›ธ character in /// . - pub cm_gc: CM_GC(1 << 32) = false; + pub cm_gc: CM_GC(1 << 33) = false; /// Subset of the reference-types WebAssembly proposal which only /// encompasses the leb-encoding of the table immediate to the @@ -288,13 +293,13 @@ define_wasm_features! { /// integer for example. /// /// This is a subcomponent of the "lime1" feature. - pub call_indirect_overlong: CALL_INDIRECT_OVERLONG(1 << 33) = true; + pub call_indirect_overlong: CALL_INDIRECT_OVERLONG(1 << 34) = true; /// Subset of the bulk-memory proposal covering just the `memory.copy` /// and `memory.fill` instructions. /// /// This is a subcomponent of the "lime1" feature. - pub bulk_memory_opt: BULK_MEMORY_OPT(1 << 34) = true; + pub bulk_memory_opt: BULK_MEMORY_OPT(1 << 35) = true; } } diff --git a/crates/wasmparser/src/readers/component/canonicals.rs b/crates/wasmparser/src/readers/component/canonicals.rs index 2df2e9d1e6..50132d9392 100644 --- a/crates/wasmparser/src/readers/component/canonicals.rs +++ b/crates/wasmparser/src/readers/component/canonicals.rs @@ -110,9 +110,9 @@ pub enum CanonicalFunction { ContextSet(u32), /// A function which yields control to the host so that other tasks are able /// to make progress, if any. - Yield { + ThreadYield { /// If `true`, indicates the caller instance maybe reentered. - async_: bool, + cancellable: bool, }, /// A function to drop a specified task which has completed. SubtaskDrop, @@ -246,7 +246,7 @@ pub enum CanonicalFunction { WaitableSetWait { /// Whether or not the guest can be reentered while calling this /// function. - async_: bool, + cancellable: bool, /// Which memory the results of this operation are stored in. memory: u32, }, @@ -254,7 +254,7 @@ pub enum CanonicalFunction { WaitableSetPoll { /// Whether or not the guest can be reentered while calling this /// function. - async_: bool, + cancellable: bool, /// Which memory the results of this operation are stored in. memory: u32, }, @@ -262,6 +262,32 @@ pub enum CanonicalFunction { WaitableSetDrop, /// A function to add an item to a `waitable-set`. WaitableJoin, + /// A function to get the index of the current thread. + ThreadIndex, + /// A function to create a new thread with the specified start function. + ThreadNewIndirect { + /// The index of the function type to use as the start function. + func_ty_index: u32, + /// The index of the table to use. + table_index: u32, + }, + /// A function to suspend the current thread and switch to the given thread. + ThreadSwitchTo { + /// Whether or not the thread can be cancelled while awaiting resumption. + cancellable: bool, + }, + /// A function to suspend the current thread, immediately yielding to any transitive async-lowered calling component. + ThreadSuspend { + /// Whether or not the thread can be cancelled while suspended. + cancellable: bool, + }, + /// A function to schedule the given thread to be resumed later. + ThreadResumeLater, + /// A function to suspend the current thread and switch to the given thread. + ThreadYieldTo { + /// Whether or not the thread can be cancelled while yielding. + cancellable: bool, + }, } /// A reader for the canonical section of a WebAssembly component. @@ -310,8 +336,8 @@ impl<'a> FromReader<'a> for CanonicalFunction { 0x7f => CanonicalFunction::ContextSet(reader.read_var_u32()?), x => return reader.invalid_leading_byte(x, "context.set intrinsic type"), }, - 0x0c => CanonicalFunction::Yield { - async_: reader.read()?, + 0x0c => CanonicalFunction::ThreadYield { + cancellable: reader.read()?, }, 0x0d => CanonicalFunction::SubtaskDrop, 0x0e => CanonicalFunction::StreamNew { ty: reader.read()? }, @@ -362,15 +388,30 @@ impl<'a> FromReader<'a> for CanonicalFunction { 0x1f => CanonicalFunction::WaitableSetNew, 0x20 => CanonicalFunction::WaitableSetWait { - async_: reader.read()?, + cancellable: reader.read()?, memory: reader.read()?, }, 0x21 => CanonicalFunction::WaitableSetPoll { - async_: reader.read()?, + cancellable: reader.read()?, memory: reader.read()?, }, 0x22 => CanonicalFunction::WaitableSetDrop, 0x23 => CanonicalFunction::WaitableJoin, + 0x26 => CanonicalFunction::ThreadIndex, + 0x27 => CanonicalFunction::ThreadNewIndirect { + func_ty_index: reader.read()?, + table_index: reader.read()?, + }, + 0x28 => CanonicalFunction::ThreadSwitchTo { + cancellable: reader.read()?, + }, + 0x29 => CanonicalFunction::ThreadSuspend { + cancellable: reader.read()?, + }, + 0x2a => CanonicalFunction::ThreadResumeLater, + 0x2b => CanonicalFunction::ThreadYieldTo { + cancellable: reader.read()?, + }, 0x06 => CanonicalFunction::SubtaskCancel { async_: reader.read()?, }, diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index e2e58330cc..b1948dc150 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -364,7 +364,10 @@ impl CanonicalOptions { Concurrency::Sync => {} Concurrency::Async { callback: None } if !state.features.cm_async_stackful() => { - bail!(offset, "requires the async stackful feature") + bail!( + offset, + "requires the component model async stackful feature" + ) } Concurrency::Async { callback: None } => {} @@ -1189,7 +1192,9 @@ impl ComponentState { CanonicalFunction::TaskCancel => self.task_cancel(types, offset), CanonicalFunction::ContextGet(i) => self.context_get(i, types, offset), CanonicalFunction::ContextSet(i) => self.context_set(i, types, offset), - CanonicalFunction::Yield { async_ } => self.yield_(async_, types, offset), + CanonicalFunction::ThreadYield { cancellable } => { + self.thread_yield(cancellable, types, offset) + } CanonicalFunction::SubtaskDrop => self.subtask_drop(types, offset), CanonicalFunction::SubtaskCancel { async_ } => { self.subtask_cancel(async_, types, offset) @@ -1240,14 +1245,32 @@ impl ComponentState { } CanonicalFunction::ErrorContextDrop => self.error_context_drop(types, offset), CanonicalFunction::WaitableSetNew => self.waitable_set_new(types, offset), - CanonicalFunction::WaitableSetWait { async_, memory } => { - self.waitable_set_wait(async_, memory, types, offset) - } - CanonicalFunction::WaitableSetPoll { async_, memory } => { - self.waitable_set_poll(async_, memory, types, offset) - } + CanonicalFunction::WaitableSetWait { + cancellable, + memory, + } => self.waitable_set_wait(cancellable, memory, types, offset), + CanonicalFunction::WaitableSetPoll { + cancellable, + memory, + } => self.waitable_set_poll(cancellable, memory, types, offset), CanonicalFunction::WaitableSetDrop => self.waitable_set_drop(types, offset), CanonicalFunction::WaitableJoin => self.waitable_join(types, offset), + CanonicalFunction::ThreadIndex => self.thread_index(types, offset), + CanonicalFunction::ThreadNewIndirect { + func_ty_index, + table_index, + } => self.thread_new_indirect(func_ty_index, table_index, types, offset), + CanonicalFunction::ThreadSwitchTo { cancellable } => { + self.thread_switch_to(cancellable, types, offset) + } + CanonicalFunction::ThreadSuspend { cancellable } => { + self.thread_suspend(cancellable, types, offset) + } + CanonicalFunction::ThreadResumeLater => self.thread_resume_later(types, offset), + + CanonicalFunction::ThreadYieldTo { cancellable } => { + self.thread_yield_to(cancellable, types, offset) + } } } @@ -1449,6 +1472,23 @@ impl ComponentState { Ok(()) } + fn validate_context_immediate( + &self, + immediate: u32, + operation: &str, + offset: usize, + ) -> Result<()> { + if !self.features.cm_threading() && immediate > 0 { + bail!(offset, "`{operation}` immediate must be zero: {immediate}") + } else if immediate > 1 { + bail!( + offset, + "`{operation}` immediate must be zero or one: {immediate}" + ) + } + Ok(()) + } + fn context_get(&mut self, i: u32, types: &mut TypeAlloc, offset: usize) -> Result<()> { if !self.features.cm_async() { bail!( @@ -1456,9 +1496,7 @@ impl ComponentState { "`context.get` requires the component model async feature" ) } - if i > 0 { - bail!(offset, "`context.get` immediate must be zero: {i}") - } + self.validate_context_immediate(i, "context.get", offset)?; self.core_funcs .push(types.intern_func_type(FuncType::new([], [ValType::I32]), offset)); @@ -1472,23 +1510,29 @@ impl ComponentState { "`context.set` requires the component model async feature" ) } - if i > 0 { - bail!(offset, "`context.set` immediate must be zero: {i}") - } + self.validate_context_immediate(i, "context.set", offset)?; self.core_funcs .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); Ok(()) } - fn yield_(&mut self, async_: bool, types: &mut TypeAlloc, offset: usize) -> Result<()> { + fn thread_yield( + &mut self, + cancellable: bool, + types: &mut TypeAlloc, + offset: usize, + ) -> Result<()> { if !self.features.cm_async() { - bail!(offset, "`yield` requires the component model async feature") + bail!( + offset, + "`thread.yield` requires the component model async feature" + ) } - if async_ && !self.features.cm_async_stackful() { + if cancellable && !self.features.cm_async_stackful() { bail!( offset, - "async `yield` requires the component model async stackful feature" + "cancellable `thread.yield` requires the component model async stackful feature" ) } @@ -1617,7 +1661,7 @@ impl ComponentState { fn stream_cancel_read( &mut self, ty: u32, - async_: bool, + cancellable: bool, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { @@ -1627,7 +1671,7 @@ impl ComponentState { "`stream.cancel-read` requires the component model async feature" ) } - if async_ && !self.features.cm_async_builtins() { + if cancellable && !self.features.cm_async_builtins() { bail!( offset, "async `stream.cancel-read` requires the component model async builtins feature" @@ -1647,7 +1691,7 @@ impl ComponentState { fn stream_cancel_write( &mut self, ty: u32, - async_: bool, + cancellable: bool, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { @@ -1657,7 +1701,7 @@ impl ComponentState { "`stream.cancel-write` requires the component model async feature" ) } - if async_ && !self.features.cm_async_builtins() { + if cancellable && !self.features.cm_async_builtins() { bail!( offset, "async `stream.cancel-write` requires the component model async builtins feature" @@ -1807,7 +1851,7 @@ impl ComponentState { fn future_cancel_read( &mut self, ty: u32, - async_: bool, + cancellable: bool, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { @@ -1817,7 +1861,7 @@ impl ComponentState { "`future.cancel-read` requires the component model async feature" ) } - if async_ && !self.features.cm_async_builtins() { + if cancellable && !self.features.cm_async_builtins() { bail!( offset, "async `future.cancel-read` requires the component model async builtins feature" @@ -1837,7 +1881,7 @@ impl ComponentState { fn future_cancel_write( &mut self, ty: u32, - async_: bool, + cancellable: bool, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { @@ -1847,7 +1891,7 @@ impl ComponentState { "`future.cancel-write` requires the component model async feature" ) } - if async_ && !self.features.cm_async_builtins() { + if cancellable && !self.features.cm_async_builtins() { bail!( offset, "async `future.cancel-write` requires the component model async builtins feature" @@ -1991,7 +2035,7 @@ impl ComponentState { fn waitable_set_wait( &mut self, - async_: bool, + cancellable: bool, memory: u32, types: &mut TypeAlloc, offset: usize, @@ -2002,10 +2046,10 @@ impl ComponentState { "`waitable-set.wait` requires the component model async feature" ) } - if async_ && !self.features.cm_async_stackful() { + if cancellable && !self.features.cm_async_stackful() { bail!( offset, - "async `waitable-set.wait` requires the component model async stackful feature" + "cancellable `waitable-set.wait` requires the component model async stackful feature" ) } @@ -2018,7 +2062,7 @@ impl ComponentState { fn waitable_set_poll( &mut self, - async_: bool, + cancellable: bool, memory: u32, types: &mut TypeAlloc, offset: usize, @@ -2029,10 +2073,10 @@ impl ComponentState { "`waitable-set.poll` requires the component model async feature" ) } - if async_ && !self.features.cm_async_stackful() { + if cancellable && !self.features.cm_async_stackful() { bail!( offset, - "async `waitable-set.poll` requires the component model async stackful feature" + "cancellable `waitable-set.poll` requires the component model async stackful feature" ) } @@ -2069,6 +2113,142 @@ impl ComponentState { Ok(()) } + fn thread_index(&mut self, types: &mut TypeAlloc, offset: usize) -> Result<()> { + if !self.features.cm_threading() { + bail!( + offset, + "`thread.index` requires the component model threading feature" + ) + } + + self.core_funcs + .push(types.intern_func_type(FuncType::new([], [ValType::I32]), offset)); + Ok(()) + } + + fn thread_new_indirect( + &mut self, + func_ty_index: u32, + table_index: u32, + types: &mut TypeAlloc, + offset: usize, + ) -> Result<()> { + if !self.features.cm_threading() { + bail!( + offset, + "`thread.new_indirect` requires the component model threading feature" + ) + } + + let core_type_id = match self.core_type_at(func_ty_index, offset)? { + ComponentCoreTypeId::Sub(c) => c, + ComponentCoreTypeId::Module(_) => bail!(offset, "expected a core function type"), + }; + let sub_ty = &types[core_type_id]; + match &sub_ty.composite_type.inner { + CompositeInnerType::Func(func_ty) => { + if func_ty.params() != [ValType::I32] { + bail!( + offset, + "start function must take a single `i32` argument (currently)" + ); + } + if func_ty.results() != [] { + bail!(offset, "start function must not return any values"); + } + } + _ => bail!(offset, "start type must be a function"), + } + + let table = self.table_at(table_index, offset)?; + + SubtypeCx::table_type( + table, + &TableType { + initial: 0, + maximum: None, + table64: false, + shared: false, + element_type: RefType::FUNCREF, + }, + offset, + ) + .map_err(|mut e| { + e.add_context("table is not a 32-bit table of (ref null (func))".into()); + e + })?; + + self.core_funcs.push(types.intern_func_type( + FuncType::new([ValType::I32, ValType::I32], [ValType::I32]), + offset, + )); + Ok(()) + } + + fn thread_switch_to( + &mut self, + _cancellable: bool, + types: &mut TypeAlloc, + offset: usize, + ) -> Result<()> { + if !self.features.cm_threading() { + bail!( + offset, + "`thread.switch_to` requires the component model threading feature" + ) + } + + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [ValType::I32]), offset)); + Ok(()) + } + + fn thread_suspend( + &mut self, + _cancellable: bool, + types: &mut TypeAlloc, + offset: usize, + ) -> Result<()> { + if !self.features.cm_threading() { + bail!( + offset, + "`thread.suspend` requires the component model threading feature" + ) + } + self.core_funcs + .push(types.intern_func_type(FuncType::new([], [ValType::I32]), offset)); + Ok(()) + } + + fn thread_resume_later(&mut self, types: &mut TypeAlloc, offset: usize) -> Result<()> { + if !self.features.cm_threading() { + bail!( + offset, + "`thread.resume_later` requires the component model threading feature" + ) + } + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], []), offset)); + Ok(()) + } + + fn thread_yield_to( + &mut self, + _cancellable: bool, + types: &mut TypeAlloc, + offset: usize, + ) -> Result<()> { + if !self.features.cm_threading() { + bail!( + offset, + "`thread.yield_to` requires the component model threading feature" + ) + } + self.core_funcs + .push(types.intern_func_type(FuncType::new([ValType::I32], [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 diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 339389a538..3565bb17a1 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -974,10 +974,10 @@ impl Printer<'_, '_> { Ok(()) })?; } - CanonicalFunction::Yield { async_ } => { - self.print_intrinsic(state, "canon yield", &|me, _| { - if async_ { - me.print_type_keyword(" async")?; + CanonicalFunction::ThreadYield { cancellable } => { + self.print_intrinsic(state, "canon thread.yield", &|me, _| { + if cancellable { + me.print_type_keyword(" cancellable")?; } Ok(()) })?; @@ -1101,20 +1101,26 @@ impl Printer<'_, '_> { CanonicalFunction::WaitableSetNew => { self.print_intrinsic(state, "canon waitable-set.new", &|_, _| Ok(()))?; } - CanonicalFunction::WaitableSetWait { async_, memory } => { + CanonicalFunction::WaitableSetWait { + cancellable, + memory, + } => { self.print_intrinsic(state, "canon waitable-set.wait ", &|me, state| { - if async_ { - me.result.write_str("async ")?; + if cancellable { + me.result.write_str("cancellable ")?; } me.start_group("memory ")?; me.print_idx(&state.core.memory_names, memory)?; me.end_group() })?; } - CanonicalFunction::WaitableSetPoll { async_, memory } => { + CanonicalFunction::WaitableSetPoll { + cancellable, + memory, + } => { self.print_intrinsic(state, "canon waitable-set.poll ", &|me, state| { - if async_ { - me.result.write_str("async ")?; + if cancellable { + me.result.write_str("cancellable ")?; } me.start_group("memory ")?; me.print_idx(&state.core.memory_names, memory)?; @@ -1127,6 +1133,48 @@ impl Printer<'_, '_> { CanonicalFunction::WaitableJoin => { self.print_intrinsic(state, "canon waitable.join", &|_, _| Ok(()))?; } + CanonicalFunction::ThreadIndex => { + self.print_intrinsic(state, "canon thread.index", &|_, _| Ok(()))?; + } + CanonicalFunction::ThreadNewIndirect { + func_ty_index, + table_index, + } => { + self.print_intrinsic(state, "canon thread.new_indirect ", &|me, state| { + me.print_idx(&state.core.type_names, func_ty_index)?; + me.result.write_str(" ")?; + me.start_group("table ")?; + me.print_idx(&state.core.table_names, table_index)?; + me.end_group() + })?; + } + CanonicalFunction::ThreadSwitchTo { cancellable } => { + self.print_intrinsic(state, "canon thread.switch-to", &|me, _| { + if cancellable { + me.result.write_str(" cancellable")?; + } + Ok(()) + })?; + } + CanonicalFunction::ThreadSuspend { cancellable } => { + self.print_intrinsic(state, "canon thread.suspend", &|me, _| { + if cancellable { + me.result.write_str(" cancellable")?; + } + Ok(()) + })?; + } + CanonicalFunction::ThreadResumeLater => { + self.print_intrinsic(state, "canon thread.resume-later", &|_, _| Ok(()))?; + } + CanonicalFunction::ThreadYieldTo { cancellable } => { + self.print_intrinsic(state, "canon thread.yield-to", &|me, _| { + if cancellable { + me.result.write_str(" cancellable")?; + } + Ok(()) + })?; + } } } diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index 64d717d245..6b14e7db6b 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -395,9 +395,9 @@ impl<'a> Encoder<'a> { self.core_func_names.push(name); self.funcs.context_set(*i); } - CoreFuncKind::Yield(info) => { + CoreFuncKind::ThreadYield(info) => { self.core_func_names.push(name); - self.funcs.yield_(info.async_); + self.funcs.thread_yield(info.cancellable); } CoreFuncKind::SubtaskDrop => { self.core_func_names.push(name); @@ -503,6 +503,31 @@ impl<'a> Encoder<'a> { self.core_func_names.push(name); self.funcs.waitable_join(); } + CoreFuncKind::ThreadIndex => { + self.core_func_names.push(name); + self.funcs.thread_index(); + } + CoreFuncKind::ThreadNewIndirect(info) => { + self.core_func_names.push(name); + self.funcs + .thread_new_indirect(info.ty.into(), info.table.idx.into()); + } + CoreFuncKind::ThreadSwitchTo(info) => { + self.core_func_names.push(name); + self.funcs.thread_switch_to(info.cancellable); + } + CoreFuncKind::ThreadSuspend(info) => { + self.core_func_names.push(name); + self.funcs.thread_suspend(info.cancellable); + } + CoreFuncKind::ThreadResumeLater => { + self.core_func_names.push(name); + self.funcs.thread_resume_later(); + } + CoreFuncKind::ThreadYieldTo(info) => { + self.core_func_names.push(name); + self.funcs.thread_yield_to(info.cancellable); + } }, } diff --git a/crates/wast/src/component/func.rs b/crates/wast/src/component/func.rs index e967bfbb65..dfed403781 100644 --- a/crates/wast/src/component/func.rs +++ b/crates/wast/src/component/func.rs @@ -59,7 +59,7 @@ pub enum CoreFuncKind<'a> { TaskCancel, ContextGet(u32), ContextSet(u32), - Yield(CanonYield), + ThreadYield(CanonThreadYield), SubtaskDrop, SubtaskCancel(CanonSubtaskCancel), StreamNew(CanonStreamNew<'a>), @@ -84,6 +84,12 @@ pub enum CoreFuncKind<'a> { WaitableSetPoll(CanonWaitableSetPoll<'a>), WaitableSetDrop, WaitableJoin, + ThreadIndex, + ThreadNewIndirect(CanonThreadNewIndirect<'a>), + ThreadSwitchTo(CanonThreadSwitchTo), + ThreadSuspend(CanonThreadSuspend), + ThreadResumeLater, + ThreadYieldTo(CanonThreadYieldTo), } impl<'a> Parse<'a> for CoreFuncKind<'a> { @@ -134,8 +140,8 @@ impl<'a> CoreFuncKind<'a> { parser.parse::()?; parser.parse::()?; Ok(CoreFuncKind::ContextSet(parser.parse()?)) - } else if l.peek::()? { - Ok(CoreFuncKind::Yield(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::ThreadYield(parser.parse()?)) } else if l.peek::()? { parser.parse::()?; Ok(CoreFuncKind::SubtaskDrop) @@ -189,6 +195,20 @@ impl<'a> CoreFuncKind<'a> { } else if l.peek::()? { parser.parse::()?; Ok(CoreFuncKind::WaitableJoin) + } else if l.peek::()? { + parser.parse::()?; + Ok(CoreFuncKind::ThreadIndex) + } else if l.peek::()? { + Ok(CoreFuncKind::ThreadNewIndirect(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::ThreadSwitchTo(parser.parse()?)) + } else if l.peek::()? { + Ok(CoreFuncKind::ThreadSuspend(parser.parse()?)) + } else if l.peek::()? { + parser.parse::()?; + Ok(CoreFuncKind::ThreadResumeLater) + } else if l.peek::()? { + Ok(CoreFuncKind::ThreadYieldTo(parser.parse()?)) } else { Err(l.error()) } @@ -569,7 +589,7 @@ pub struct CanonWaitableSetWait<'a> { impl<'a> Parse<'a> for CanonWaitableSetWait<'a> { fn parse(parser: Parser<'a>) -> Result { parser.parse::()?; - let async_ = parser.parse::>()?.is_some(); + let async_ = parser.parse::>()?.is_some(); let memory = parser.parens(|p| p.parse())?; Ok(Self { async_, memory }) @@ -589,27 +609,27 @@ pub struct CanonWaitableSetPoll<'a> { impl<'a> Parse<'a> for CanonWaitableSetPoll<'a> { fn parse(parser: Parser<'a>) -> Result { parser.parse::()?; - let async_ = parser.parse::>()?.is_some(); + let async_ = parser.parse::>()?.is_some(); let memory = parser.parens(|p| p.parse())?; Ok(Self { async_, memory }) } } -/// Information relating to the `yield` intrinsic. +/// Information relating to the `thread.yield` intrinsic. #[derive(Debug)] -pub struct CanonYield { +pub struct CanonThreadYield { /// If true, the component instance may be reentered during a call to this /// intrinsic. - pub async_: bool, + pub cancellable: bool, } -impl<'a> Parse<'a> for CanonYield { +impl<'a> Parse<'a> for CanonThreadYield { fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; - let async_ = parser.parse::>()?.is_some(); + parser.parse::()?; + let cancellable = parser.parse::>()?.is_some(); - Ok(Self { async_ }) + Ok(Self { cancellable }) } } @@ -930,6 +950,67 @@ impl<'a> Parse<'a> for CanonErrorContextDebugMessage<'a> { } } +/// Information relating to the `thread.new_indirect` intrinsic. +#[derive(Debug)] +pub struct CanonThreadNewIndirect<'a> { + /// The function type for the thread start function. + pub ty: Index<'a>, + /// The table to index. + pub table: CoreItemRef<'a, kw::table>, +} + +impl<'a> Parse<'a> for CanonThreadNewIndirect<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let ty = parser.parse()?; + let table = parser.parens(|p| p.parse())?; + Ok(Self { ty, table }) + } +} + +/// Information relating to the `thread.switch-to` intrinsic. +#[derive(Debug)] +pub struct CanonThreadSwitchTo { + /// Whether the thread can be cancelled while suspended at this point. + pub cancellable: bool, +} + +impl<'a> Parse<'a> for CanonThreadSwitchTo { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let cancellable = parser.parse::>()?.is_some(); + Ok(Self { cancellable }) + } +} + +/// Information relating to the `thread.suspend` intrinsic. +#[derive(Debug)] +pub struct CanonThreadSuspend { + /// Whether the thread can be cancelled while suspended at this point. + pub cancellable: bool, +} +impl<'a> Parse<'a> for CanonThreadSuspend { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let cancellable = parser.parse::>()?.is_some(); + Ok(Self { cancellable }) + } +} + +/// Information relating to the `thread.yield-to` intrinsic. +#[derive(Debug)] +pub struct CanonThreadYieldTo { + /// Whether the thread can be cancelled while yielding at this point. + pub cancellable: bool, +} +impl<'a> Parse<'a> for CanonThreadYieldTo { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let cancellable = parser.parse::>()?.is_some(); + Ok(Self { cancellable }) + } +} + #[derive(Debug)] /// Canonical ABI options. pub enum CanonOpt<'a> { diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs index 54e6f3e272..aff8b38694 100644 --- a/crates/wast/src/component/resolve.rs +++ b/crates/wast/src/component/resolve.rs @@ -397,7 +397,7 @@ impl<'a> Resolver<'a> { CoreFuncKind::ThreadAvailableParallelism(_) | CoreFuncKind::BackpressureSet | CoreFuncKind::TaskCancel - | CoreFuncKind::Yield(_) + | CoreFuncKind::ThreadYield(_) | CoreFuncKind::SubtaskDrop | CoreFuncKind::SubtaskCancel(_) | CoreFuncKind::ErrorContextDrop => {} @@ -469,6 +469,15 @@ impl<'a> Resolver<'a> { } CoreFuncKind::WaitableSetDrop => {} CoreFuncKind::WaitableJoin => {} + CoreFuncKind::ThreadIndex => {} + CoreFuncKind::ThreadNewIndirect(info) => { + self.resolve_ns(&mut info.ty, Ns::CoreType)?; + self.core_item_ref(&mut info.table)?; + } + CoreFuncKind::ThreadSwitchTo(_) => {} + CoreFuncKind::ThreadSuspend(_) => {} + CoreFuncKind::ThreadResumeLater => {} + CoreFuncKind::ThreadYieldTo(_) => {} }, } diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index d1fdbe6380..ee6f5a40f7 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -563,7 +563,7 @@ pub mod kw { custom_keyword!(backpressure_set = "backpressure.set"); custom_keyword!(task_return = "task.return"); custom_keyword!(task_cancel = "task.cancel"); - custom_keyword!(yield_ = "yield"); + custom_keyword!(thread_yield = "thread.yield"); custom_keyword!(subtask_drop = "subtask.drop"); custom_keyword!(subtask_cancel = "subtask.cancel"); custom_keyword!(stream_new = "stream.new"); @@ -597,6 +597,13 @@ pub mod kw { custom_keyword!(waitable_join = "waitable.join"); custom_keyword!(context_get = "context.get"); custom_keyword!(context_set = "context.set"); + custom_keyword!(thread_index = "thread.index"); + custom_keyword!(thread_new_indirect = "thread.new_indirect"); + custom_keyword!(thread_switch_to = "thread.switch-to"); + custom_keyword!(thread_suspend = "thread.suspend"); + custom_keyword!(thread_resume_later = "thread.resume-later"); + custom_keyword!(thread_yield_to = "thread.yield-to"); + custom_keyword!(cancellable); } /// Common annotations used to parse WebAssembly text files. diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs index 50341052a8..940adb1eb5 100644 --- a/crates/wit-component/src/dummy.rs +++ b/crates/wit-component/src/dummy.rs @@ -379,7 +379,7 @@ fn push_root_async_intrinsics(dst: &mut String) { (import "$root" "[waitable-set-poll]" (func (param i32 i32) (result i32))) (import "$root" "[waitable-set-drop]" (func (param i32))) (import "$root" "[waitable-join]" (func (param i32 i32))) -(import "$root" "[yield]" (func (result i32))) +(import "$root" "[thread-yield]" (func (result i32))) (import "$root" "[subtask-drop]" (func (param i32))) (import "$root" "[subtask-cancel]" (func (param i32) (result i32))) (import "$root" "[error-context-new-utf8]" (func (param i32 i32) (result i32))) @@ -392,10 +392,12 @@ fn push_root_async_intrinsics(dst: &mut String) { (import "$root" "[context-get-0]" (func (result i32))) (import "$root" "[context-set-0]" (func (param i32))) -;; deferred behind ๐Ÿš or ๐ŸšŸ upstream -;;(import "$root" "[async-lower][waitable-set-wait]" (func (param i32 i32) (result i32))) -;;(import "$root" "[async-lower][waitable-set-poll]" (func (param i32 i32) (result i32))) -;;(import "$root" "[async-lower][yield]" (func (result i32))) +;; deferred behind ๐Ÿงต upstream +;;(import "$root" "[cancellable][waitable-set-wait]" (func (param i32 i32) (result i32))) +;;(import "$root" "[cancellable][waitable-set-poll]" (func (param i32 i32) (result i32))) +;;(import "$root" "[cancellable][thread-yield]" (func (result i32))) +;;(import "$root" "[context-get-1]" (func (result i32))) +;;(import "$root" "[context-set-1]" (func (param i32))) "#, ); } diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 176584ec75..9c085e4756 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -671,7 +671,8 @@ impl<'a> EncodingState<'a> { | Export::GeneralPurposeExportRealloc | Export::GeneralPurposeImportRealloc | Export::Initialize - | Export::ReallocForAdapter => continue, + | Export::ReallocForAdapter + | Export::IndirectFunctionTable => continue, } } @@ -1319,12 +1320,12 @@ impl<'a> EncodingState<'a> { } } - ShimKind::WaitableSetWait { async_ } => self + ShimKind::WaitableSetWait { cancellable } => self .component - .waitable_set_wait(*async_, self.memory_index.unwrap()), - ShimKind::WaitableSetPoll { async_ } => self + .waitable_set_wait(*cancellable, self.memory_index.unwrap()), + ShimKind::WaitableSetPoll { cancellable } => self .component - .waitable_set_poll(*async_, self.memory_index.unwrap()), + .waitable_set_poll(*cancellable, self.memory_index.unwrap()), ShimKind::ErrorContextNew { encoding } => self.component.error_context_new( shim.options.into_iter(*encoding, self.memory_index, None)?, ), @@ -1374,6 +1375,28 @@ impl<'a> EncodingState<'a> { .into_iter(*encoding, self.memory_index, realloc_index)?; self.component.task_return(result, options) } + ShimKind::ThreadNewIndirect { + for_module, + func_ty, + } => { + // Encode the function type for the thread start function so we can reference it in the `canon` call. + let (func_ty_idx, f) = self.component.core_type(); + f.core().func_type(func_ty); + + // In order for the funcref table referenced by `thread.new_indirect` to be used, + // it must have been exported by the module. + let exports = self.info.exports_for(*for_module); + let instance_index = self.instance_for(*for_module); + let table_idx = exports.indirect_function_table().map(|table| { + self.core_alias_export(instance_index, table, ExportKind::Table) + }).ok_or_else(|| { + anyhow!( + "table __indirect_function_table must be an exported funcref table for thread.new_indirect" + ) + })?; + + self.component.thread_new_indirect(func_ty_idx, table_idx) + } }; exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); @@ -1692,16 +1715,20 @@ impl<'a> EncodingState<'a> { let index = self.component.backpressure_set(); Ok((ExportKind::Func, index)) } - Import::WaitableSetWait { async_ } => { - Ok(self - .materialize_shim_import(shims, &ShimKind::WaitableSetWait { async_: *async_ })) - } - Import::WaitableSetPoll { async_ } => { - Ok(self - .materialize_shim_import(shims, &ShimKind::WaitableSetPoll { async_: *async_ })) - } - Import::Yield { async_ } => { - let index = self.component.yield_(*async_); + Import::WaitableSetWait { cancellable } => Ok(self.materialize_shim_import( + shims, + &ShimKind::WaitableSetWait { + cancellable: *cancellable, + }, + )), + Import::WaitableSetPoll { cancellable } => Ok(self.materialize_shim_import( + shims, + &ShimKind::WaitableSetPoll { + cancellable: *cancellable, + }, + )), + Import::ThreadYield { cancellable } => { + let index = self.component.thread_yield(*cancellable); Ok((ExportKind::Func, index)) } Import::SubtaskDrop => { @@ -1839,6 +1866,34 @@ impl<'a> EncodingState<'a> { let index = self.component.task_cancel(); Ok((ExportKind::Func, index)) } + Import::ThreadIndex => { + let index = self.component.thread_index(); + Ok((ExportKind::Func, index)) + } + Import::ThreadNewIndirect => Ok(self.materialize_shim_import( + shims, + &ShimKind::ThreadNewIndirect { + for_module, + // This is fixed for now + func_ty: FuncType::new([ValType::I32], []), + }, + )), + Import::ThreadSwitchTo { cancellable } => { + let index = self.component.thread_switch_to(*cancellable); + Ok((ExportKind::Func, index)) + } + Import::ThreadSuspend { cancellable } => { + let index = self.component.thread_suspend(*cancellable); + Ok((ExportKind::Func, index)) + } + Import::ThreadResumeLater => { + let index = self.component.thread_resume_later(); + Ok((ExportKind::Func, index)) + } + Import::ThreadYieldTo { cancellable } => { + let index = self.component.thread_yield_to(*cancellable); + Ok((ExportKind::Func, index)) + } } } @@ -2119,11 +2174,11 @@ enum ShimKind<'a> { /// A shim used for the `waitable-set.wait` built-in function, which must /// refer to the core module instance's memory to which results will be /// written. - WaitableSetWait { async_: bool }, + WaitableSetWait { cancellable: bool }, /// A shim used for the `waitable-set.poll` built-in function, which must /// refer to the core module instance's memory to which results will be /// written. - WaitableSetPoll { async_: bool }, + WaitableSetPoll { cancellable: bool }, /// Shim for `task.return` to handle a reference to a `memory` which may TaskReturn { /// The interface (optional) that owns `func` below. If `None` then it's @@ -2155,6 +2210,14 @@ enum ShimKind<'a> { /// The string encoding to use when lowering the debug message. encoding: StringEncoding, }, + /// A shim used for the `thread.new_indirect` built-in function, which + /// must refer to the core module instance's indirect function table. + ThreadNewIndirect { + /// Which instance to pull the function table from. + for_module: CustomModule<'a>, + /// The function type to use when creating the thread. + func_ty: FuncType, + }, } /// Indicator for which module is being used for a lowering or where options @@ -2204,7 +2267,7 @@ impl<'a> Shims<'a> { | Import::ExportedTaskCancel | Import::ErrorContextDrop | Import::BackpressureSet - | Import::Yield { .. } + | Import::ThreadYield { .. } | Import::SubtaskDrop | Import::SubtaskCancel { .. } | Import::FutureNew(..) @@ -2221,7 +2284,12 @@ impl<'a> Shims<'a> { | Import::WaitableSetDrop | Import::WaitableJoin | Import::ContextGet(_) - | Import::ContextSet(_) => {} + | Import::ContextSet(_) + | Import::ThreadIndex + | Import::ThreadSwitchTo { .. } + | Import::ThreadSuspend { .. } + | Import::ThreadResumeLater + | Import::ThreadYieldTo { .. } => {} // If `task.return` needs to be indirect then generate a shim // for it, otherwise skip the shim and let it get materialized @@ -2306,13 +2374,15 @@ impl<'a> Shims<'a> { ); } - Import::WaitableSetWait { async_ } => { + Import::WaitableSetWait { cancellable } => { let name = self.shims.len().to_string(); self.push(Shim { name, debug_name: "waitable-set.wait".to_string(), options: RequiredOptions::empty(), - kind: ShimKind::WaitableSetWait { async_: *async_ }, + kind: ShimKind::WaitableSetWait { + cancellable: *cancellable, + }, sig: WasmSignature { params: vec![WasmType::I32; 2], results: vec![WasmType::I32], @@ -2322,13 +2392,15 @@ impl<'a> Shims<'a> { }); } - Import::WaitableSetPoll { async_ } => { + Import::WaitableSetPoll { cancellable } => { let name = self.shims.len().to_string(); self.push(Shim { name, debug_name: "waitable-set.poll".to_string(), options: RequiredOptions::empty(), - kind: ShimKind::WaitableSetPoll { async_: *async_ }, + kind: ShimKind::WaitableSetPoll { + cancellable: *cancellable, + }, sig: WasmSignature { params: vec![WasmType::I32; 2], results: vec![WasmType::I32], @@ -2377,6 +2449,26 @@ impl<'a> Shims<'a> { }); } + Import::ThreadNewIndirect => { + let name = self.shims.len().to_string(); + self.push(Shim { + name, + debug_name: "thread.new_indirect".to_string(), + options: RequiredOptions::empty(), + kind: ShimKind::ThreadNewIndirect { + for_module, + // This is fixed for now + func_ty: FuncType::new([ValType::I32], vec![]), + }, + sig: WasmSignature { + params: vec![WasmType::I32; 2], + results: vec![WasmType::I32], + indirect_params: false, + retptr: false, + }, + }); + } + // Adapter imports into the main module must got through an // indirection, so that's registered here. Import::AdapterExport { adapter, func, ty } => { diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index fbfe7df842..81aa2684aa 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -421,13 +421,19 @@ impl<'a> ComponentWorld<'a> { | Import::WaitableSetPoll { .. } | Import::WaitableSetDrop | Import::WaitableJoin - | Import::Yield { .. } + | Import::ThreadYield { .. } | Import::SubtaskDrop | Import::SubtaskCancel { .. } | Import::ErrorContextNew { .. } | Import::ErrorContextDebugMessage { .. } | Import::ErrorContextDrop - | Import::ExportedTaskCancel => {} + | Import::ExportedTaskCancel + | Import::ThreadIndex + | Import::ThreadNewIndirect { .. } + | Import::ThreadSwitchTo { .. } + | Import::ThreadSuspend { .. } + | Import::ThreadResumeLater + | Import::ThreadYieldTo { .. } => {} } } } diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index 0a990fd0db..3d20c3e5c9 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -286,14 +286,14 @@ pub enum Import { /// 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. - WaitableSetWait { async_: bool }, + WaitableSetWait { cancellable: bool }, /// A `canon waitable.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. - WaitableSetPoll { async_: bool }, + WaitableSetPoll { cancellable: bool }, /// A `waitable-set.drop` intrinsic. WaitableSetDrop, @@ -301,11 +301,11 @@ pub enum Import { /// A `waitable.join` intrinsic. WaitableJoin, - /// A `canon yield` intrinsic. + /// A `canon thread.yield` intrinsic. /// /// This allows the guest to yield (e.g. during an computationally-intensive /// operation) and allow other subtasks to make progress. - Yield { async_: bool }, + ThreadYield { cancellable: bool }, /// A `canon subtask.drop` intrinsic. /// @@ -412,6 +412,37 @@ pub enum Import { /// This allows the guest to release its handle to the specified /// `error-context` instance. ErrorContextDrop, + + /// A `canon thread.index` intrinsic. + /// + /// This allows the guest to get the index of the current thread. + ThreadIndex, + + /// A `canon thread.new_indirect` intrinsic. + /// + /// This allows the guest to create a new thread running a specified function. + ThreadNewIndirect, + + /// A `canon thread.switch-to` intrinsic. + /// + /// This allows the guest to switch execution to another thread. + ThreadSwitchTo { cancellable: bool }, + + /// A `canon thread.suspend` intrinsic. + /// + /// This allows the guest to suspend the current thread, switching execution to + /// an unspecified thread. + ThreadSuspend { cancellable: bool }, + + /// A `canon thread.resume-later` intrinsic. + /// + /// This allows the guest to mark a suspended thread for later resumption. + ThreadResumeLater, + + /// A `canon thread.yield-to` intrinsic. + /// + /// This allows the guest to suspend, yielding execution to a specified thread. + ThreadYieldTo { cancellable: bool }, } impl ImportMap { @@ -553,129 +584,143 @@ impl ImportMap { let world_id = encoder.metadata.world; let world = &resolve.worlds[world_id]; - let (async_, name) = if let Some(name) = names.async_lower_name(name) { - (true, name) - } else { - (false, name) - }; - let abi = if async_ { - AbiVariant::GuestImportAsync - } else { - AbiVariant::GuestImport - }; - let validate_not_async = || { - if async_ { - bail!("`{name}` cannot be marked `async`") - } - Ok(()) - }; - if module == names.import_root() { - if Some(name) == names.error_context_drop() { - validate_not_async()?; + if names.error_context_drop(name) { let expected = FuncType::new([ValType::I32], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::ErrorContextDrop); } - if Some(name) == names.backpressure_set() { - validate_not_async()?; + if names.backpressure_set(name) { let expected = FuncType::new([ValType::I32], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::BackpressureSet); } - if Some(name) == names.waitable_set_new() { - validate_not_async()?; + if names.waitable_set_new(name) { let expected = FuncType::new([], [ValType::I32]); validate_func_sig(name, &expected, ty)?; return Ok(Import::WaitableSetNew); } - if Some(name) == names.waitable_set_wait() { + if let Some(info) = names.waitable_set_wait(name) { let expected = FuncType::new([ValType::I32; 2], [ValType::I32]); validate_func_sig(name, &expected, ty)?; return Ok(Import::WaitableSetWait { - async_: abi == AbiVariant::GuestImportAsync, + cancellable: info.cancellable, }); } - if Some(name) == names.waitable_set_poll() { + if let Some(info) = names.waitable_set_poll(name) { let expected = FuncType::new([ValType::I32; 2], [ValType::I32]); validate_func_sig(name, &expected, ty)?; return Ok(Import::WaitableSetPoll { - async_: abi == AbiVariant::GuestImportAsync, + cancellable: info.cancellable, }); } - if Some(name) == names.waitable_set_drop() { - validate_not_async()?; + if names.waitable_set_drop(name) { let expected = FuncType::new([ValType::I32], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::WaitableSetDrop); } - if Some(name) == names.waitable_join() { - validate_not_async()?; + if names.waitable_join(name) { let expected = FuncType::new([ValType::I32; 2], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::WaitableJoin); } - if Some(name) == names.yield_() { + if let Some(info) = names.thread_yield(name) { let expected = FuncType::new([], [ValType::I32]); validate_func_sig(name, &expected, ty)?; - return Ok(Import::Yield { async_ }); + return Ok(Import::ThreadYield { + cancellable: info.cancellable, + }); } - if Some(name) == names.subtask_drop() { - validate_not_async()?; + if names.subtask_drop(name) { let expected = FuncType::new([ValType::I32], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::SubtaskDrop); } - if Some(name) == names.subtask_cancel() { + if let Some(info) = names.subtask_cancel(name) { let expected = FuncType::new([ValType::I32], [ValType::I32]); validate_func_sig(name, &expected, ty)?; - return Ok(Import::SubtaskCancel { async_ }); + return Ok(Import::SubtaskCancel { + async_: info.async_lowered, + }); } if let Some(encoding) = names.error_context_new(name) { - validate_not_async()?; let expected = FuncType::new([ValType::I32; 2], [ValType::I32]); validate_func_sig(name, &expected, ty)?; return Ok(Import::ErrorContextNew { encoding }); } if let Some(encoding) = names.error_context_debug_message(name) { - validate_not_async()?; let expected = FuncType::new([ValType::I32; 2], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::ErrorContextDebugMessage { encoding }); } if let Some(i) = names.context_get(name) { - validate_not_async()?; let expected = FuncType::new([], [ValType::I32]); validate_func_sig(name, &expected, ty)?; return Ok(Import::ContextGet(i)); } if let Some(i) = names.context_set(name) { - validate_not_async()?; let expected = FuncType::new([ValType::I32], []); validate_func_sig(name, &expected, ty)?; return Ok(Import::ContextSet(i)); } + if names.thread_index(name) { + let expected = FuncType::new([], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ThreadIndex); + } + if names.thread_new_indirect(name) { + let expected = FuncType::new([ValType::I32; 2], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ThreadNewIndirect); + } + if let Some(info) = names.thread_switch_to(name) { + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ThreadSwitchTo { + cancellable: info.cancellable, + }); + } + if let Some(info) = names.thread_suspend(name) { + let expected = FuncType::new([], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ThreadSuspend { + cancellable: info.cancellable, + }); + } + if names.thread_resume_later(name) { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ThreadResumeLater); + } + if let Some(info) = names.thread_yield_to(name) { + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ThreadYieldTo { + cancellable: info.cancellable, + }); + } - let key = WorldKey::Name(name.to_string()); + let (key_name, abi) = names.world_key_name_and_abi(name); + let key = WorldKey::Name(key_name.to_string()); if let Some(WorldItem::Function(func)) = world.imports.get(&key) { validate_func(resolve, ty, func, abi)?; return Ok(Import::WorldFunc(key, func.name.clone(), abi)); } if let Some(import) = - self.maybe_classify_wit_intrinsic(name, None, encoder, ty, async_, true, names)? + self.maybe_classify_wit_intrinsic(name, None, encoder, ty, true, names)? { return Ok(import); } @@ -692,7 +737,7 @@ impl ImportMap { Some(module) if module == names.import_root() ) { if let Some(import) = - self.maybe_classify_wit_intrinsic(name, None, encoder, ty, async_, false, names)? + self.maybe_classify_wit_intrinsic(name, None, encoder, ty, false, names)? { return Ok(import); } @@ -706,15 +751,9 @@ impl ImportMap { if let Some(interface) = interface.strip_prefix(names.import_exported_intrinsic_prefix()) { let (key, id) = names.module_to_interface(interface, resolve, &world.exports)?; - if let Some(import) = self.maybe_classify_wit_intrinsic( - name, - Some((key, id)), - encoder, - ty, - async_, - false, - names, - )? { + if let Some(import) = + self.maybe_classify_wit_intrinsic(name, Some((key, id)), encoder, ty, false, names)? + { return Ok(import); } bail!("unknown function `{name}`") @@ -722,7 +761,8 @@ impl ImportMap { let (key, id) = names.module_to_interface(interface, resolve, &world.imports)?; let interface = &resolve.interfaces[id]; - if let Some(f) = interface.functions.get(name) { + let (function_name, abi) = names.interface_function_name_and_abi(name); + if let Some(f) = interface.functions.get(function_name) { validate_func(resolve, ty, f, abi).with_context(|| { let name = resolve.name_world_key(&key); format!("failed to validate import interface `{name}`") @@ -730,15 +770,9 @@ impl ImportMap { return Ok(Import::InterfaceFunc(key, id, f.name.clone(), abi)); } - if let Some(import) = self.maybe_classify_wit_intrinsic( - name, - Some((key, id)), - encoder, - ty, - async_, - true, - names, - )? { + if let Some(import) = + self.maybe_classify_wit_intrinsic(name, Some((key, id)), encoder, ty, true, names)? + { return Ok(import); } bail!( @@ -758,8 +792,8 @@ impl ImportMap { /// ## Parameters /// /// * `name` - the core module name which is being pattern-matched. This - /// should be the "field" of the import. This should have the - /// "[async-lift]" prefix stripped out already. + /// should be the "field" of the import. This may include the "[async-lower]" + /// or "[cancellable]" prefixes. /// * `key_and_id` - this is the inferred "container" for the function /// being described which is inferred from the module portion of the core /// wasm import field. This is `None` for root-level function/type @@ -770,8 +804,6 @@ impl ImportMap { /// * `encoder` - this is the encoder state that contains /// `Resolve`/metadata information. /// * `ty` - the core wasm type of this import. - /// * `async_` - whether or not this import had the `[async-lift]` import. - /// Note that such prefix is not present in `name`. /// * `import` - whether or not this core wasm import is operating on a WIT /// level import or export. An example of this being an export is when a /// core module imports a destructor for an exported resource. @@ -782,7 +814,6 @@ impl ImportMap { key_and_id: Option<(WorldKey, InterfaceId)>, encoder: &ComponentEncoder, ty: &FuncType, - async_: bool, import: bool, names: &dyn NameMangling, ) -> Result> { @@ -807,9 +838,6 @@ impl ImportMap { // Test whether this is a `resource.drop` intrinsic. if let Some(resource) = names.resource_drop_name(name) { - if async_ { - bail!("async `resource.drop` calls not supported"); - } if let Some(resource_id) = resource_test(resource) { let key = key.unwrap_or_else(|| WorldKey::Name(resource.to_string())); let expected = FuncType::new([ValType::I32], []); @@ -853,108 +881,88 @@ impl ImportMap { func.result, ))); } - if Some(name) == names.task_cancel() { - if async_ { - bail!("async `task.cancel` calls not supported"); - } + if names.task_cancel(name) { let expected = FuncType::new([], []); validate_func_sig(name, &expected, ty)?; return Ok(Some(Import::ExportedTaskCancel)); } } - // Looks for `[$prefix-N]foo` within `name`. If found then `foo` is - // used to find a function within `id` and `world` above. Once found - // then `N` is used to index within that function to extract a - // future/stream type. If that's all found then a `PayloadInfo` is - // returned to get attached to an intrinsic. - let prefixed_payload = |prefix: &str| { - // parse the `prefix` into `func_name` and `type_index`, bailing out - // with `None` if anything doesn't match. - let (type_index, func_name) = prefixed_integer(name, prefix)?; - let type_index = type_index as usize; - - // Double-check that `func_name` is indeed a function name within - // this interface/world. Then additionally double-check that - // `type_index` is indeed a valid index for this function's type - // signature. - let function = get_function(resolve, world, func_name, id, import).ok()?; - let ty = *function.find_futures_and_streams(resolve).get(type_index)?; - - // And if all that passes wrap up everything in a `PayloadInfo`. - Some(PayloadInfo { - name: name.to_string(), - ty, - function: function.name.clone(), - key: key - .clone() - .unwrap_or_else(|| WorldKey::Name(name.to_string())), - interface: id, - imported: import, - }) + let lookup_context = PayloadLookupContext { + resolve, + world, + key, + id, + import, }; // Test for a number of async-related intrinsics. All intrinsics are // prefixed with `[...-N]` where `...` is the name of the intrinsic and // the `N` is the indexed future/stream that is being referred to. - let import = if let Some(info) = prefixed_payload("[future-new-") { - if async_ { - bail!("async `future.new` calls not supported"); - } + let import = if let Some(info) = names.future_new(&lookup_context, name) { validate_func_sig(name, &FuncType::new([], [ValType::I64]), ty)?; Import::FutureNew(info) - } else if let Some(info) = prefixed_payload("[future-write-") { + } else if let Some(info) = names.future_write(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32; 2], [ValType::I32]), ty)?; - Import::FutureWrite { async_, info } - } else if let Some(info) = prefixed_payload("[future-read-") { + Import::FutureWrite { + async_: info.async_lowered, + info: info.inner, + } + } else if let Some(info) = names.future_read(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32; 2], [ValType::I32]), ty)?; - Import::FutureRead { async_, info } - } else if let Some(info) = prefixed_payload("[future-cancel-write-") { + Import::FutureRead { + async_: info.async_lowered, + info: info.inner, + } + } else if let Some(info) = names.future_cancel_write(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], [ValType::I32]), ty)?; - Import::FutureCancelWrite { async_, info } - } else if let Some(info) = prefixed_payload("[future-cancel-read-") { + Import::FutureCancelWrite { + async_: info.async_lowered, + info: info.inner, + } + } else if let Some(info) = names.future_cancel_read(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], [ValType::I32]), ty)?; - Import::FutureCancelRead { async_, info } - } else if let Some(info) = prefixed_payload("[future-drop-writable-") { - if async_ { - bail!("async `future.drop-writable` calls not supported"); + Import::FutureCancelRead { + async_: info.async_lowered, + info: info.inner, } + } else if let Some(info) = names.future_drop_writable(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; Import::FutureDropWritable(info) - } else if let Some(info) = prefixed_payload("[future-drop-readable-") { - if async_ { - bail!("async `future.drop-readable` calls not supported"); - } + } else if let Some(info) = names.future_drop_readable(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; Import::FutureDropReadable(info) - } else if let Some(info) = prefixed_payload("[stream-new-") { - if async_ { - bail!("async `stream.new` calls not supported"); - } + } else if let Some(info) = names.stream_new(&lookup_context, name) { validate_func_sig(name, &FuncType::new([], [ValType::I64]), ty)?; Import::StreamNew(info) - } else if let Some(info) = prefixed_payload("[stream-write-") { + } else if let Some(info) = names.stream_write(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32; 3], [ValType::I32]), ty)?; - Import::StreamWrite { async_, info } - } else if let Some(info) = prefixed_payload("[stream-read-") { + Import::StreamWrite { + async_: info.async_lowered, + info: info.inner, + } + } else if let Some(info) = names.stream_read(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32; 3], [ValType::I32]), ty)?; - Import::StreamRead { async_, info } - } else if let Some(info) = prefixed_payload("[stream-cancel-write-") { + Import::StreamRead { + async_: info.async_lowered, + info: info.inner, + } + } else if let Some(info) = names.stream_cancel_write(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], [ValType::I32]), ty)?; - Import::StreamCancelWrite { async_, info } - } else if let Some(info) = prefixed_payload("[stream-cancel-read-") { + Import::StreamCancelWrite { + async_: info.async_lowered, + info: info.inner, + } + } else if let Some(info) = names.stream_cancel_read(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], [ValType::I32]), ty)?; - Import::StreamCancelRead { async_, info } - } else if let Some(info) = prefixed_payload("[stream-drop-writable-") { - if async_ { - bail!("async `stream.drop-writable` calls not supported"); + Import::StreamCancelRead { + async_: info.async_lowered, + info: info.inner, } + } else if let Some(info) = names.stream_drop_writable(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; Import::StreamDropWritable(info) - } else if let Some(info) = prefixed_payload("[stream-drop-readable-") { - if async_ { - bail!("async `stream.drop-readable` calls not supported"); - } + } else if let Some(info) = names.stream_drop_readable(&lookup_context, name) { validate_func_sig(name, &FuncType::new([ValType::I32], []), ty)?; Import::StreamDropReadable(info) } else { @@ -1085,6 +1093,9 @@ pub enum Export { WorldFuncCallback(WorldKey), InterfaceFuncCallback(WorldKey, String), + + /// __indirect_function_table, used for `thread.new_indirect` + IndirectFunctionTable, } impl ExportMap { @@ -1164,6 +1175,12 @@ impl ExportMap { } return Ok(None); } + ExternalKind::Table => { + if Some(name) == names.export_indirect_function_table() { + return Ok(Some(Export::IndirectFunctionTable)); + } + return Ok(None); + } _ => return Ok(None), } let ty = types[types.core_function_at(export.index)].unwrap_func(); @@ -1335,6 +1352,11 @@ impl ExportMap { self.find(|m| matches!(m, Export::Memory)) } + /// Returns the indirect function table, if exported, for this module. + pub fn indirect_function_table(&self) -> Option<&str> { + self.find(|t| matches!(t, Export::IndirectFunctionTable)) + } + /// Returns the `_initialize` intrinsic, if exported, for this module. pub fn initialize(&self) -> Option<&str> { self.find(|m| matches!(m, Export::Initialize)) @@ -1440,6 +1462,29 @@ impl ExportMap { } } +/// A builtin that may be declared as cancellable. +struct MaybeCancellable { + #[allow(unused)] + inner: T, + cancellable: bool, +} + +/// A builtin that may be declared as async-lowered. +struct MaybeAsyncLowered { + inner: T, + async_lowered: bool, +} + +/// Context passed to `NameMangling` implementations of stream and future functions +/// to help with looking up payload information. +struct PayloadLookupContext<'a> { + resolve: &'a Resolve, + world: &'a World, + id: Option, + import: bool, + key: Option, +} + /// Trait dispatch and definition for parsing and interpreting "mangled names" /// which show up in imports and exports of the component model. /// @@ -1468,36 +1513,104 @@ trait NameMangling { fn export_memory(&self) -> &str; fn export_initialize(&self) -> &str; fn export_realloc(&self) -> &str; - 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_cancel(&self) -> Option<&str>; - fn backpressure_set(&self) -> Option<&str>; - fn waitable_set_new(&self) -> Option<&str>; - fn waitable_set_wait(&self) -> Option<&str>; - fn waitable_set_poll(&self) -> Option<&str>; - fn waitable_set_drop(&self) -> Option<&str>; - fn waitable_join(&self) -> Option<&str>; - fn yield_(&self) -> Option<&str>; - fn subtask_drop(&self) -> Option<&str>; - fn subtask_cancel(&self) -> Option<&str>; - fn async_lift_callback_name<'a>(&self, s: &'a str) -> Option<&'a str>; - fn async_lower_name<'a>(&self, s: &'a str) -> Option<&'a str>; - fn async_lift_name<'a>(&self, s: &'a str) -> Option<&'a str>; - fn async_lift_stackful_name<'a>(&self, s: &'a str) -> Option<&'a str>; - fn error_context_new(&self, s: &str) -> Option; - fn error_context_debug_message(&self, s: &str) -> Option; - fn error_context_drop(&self) -> Option<&str>; + fn export_indirect_function_table(&self) -> Option<&str>; + fn resource_drop_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn resource_new_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn resource_rep_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn task_return_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn task_cancel(&self, name: &str) -> bool; + fn backpressure_set(&self, name: &str) -> bool; + fn waitable_set_new(&self, name: &str) -> bool; + fn waitable_set_wait(&self, name: &str) -> Option>; + fn waitable_set_poll(&self, name: &str) -> Option>; + fn waitable_set_drop(&self, name: &str) -> bool; + fn waitable_join(&self, name: &str) -> bool; + fn thread_yield(&self, name: &str) -> Option>; + fn subtask_drop(&self, name: &str) -> bool; + fn subtask_cancel(&self, name: &str) -> Option>; + fn async_lift_callback_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn async_lift_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn async_lift_stackful_name<'a>(&self, name: &'a str) -> Option<&'a str>; + fn error_context_new(&self, name: &str) -> Option; + fn error_context_debug_message(&self, name: &str) -> Option; + fn error_context_drop(&self, name: &str) -> bool; fn context_get(&self, name: &str) -> Option; fn context_set(&self, name: &str) -> Option; + fn future_new(&self, lookup_context: &PayloadLookupContext, name: &str) -> Option; + fn future_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn future_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn future_cancel_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn future_cancel_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn future_drop_writable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option; + fn future_drop_readable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option; + fn stream_new(&self, lookup_context: &PayloadLookupContext, name: &str) -> Option; + fn stream_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn stream_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn stream_cancel_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn stream_cancel_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option>; + fn stream_drop_writable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option; + fn stream_drop_readable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option; + fn thread_index(&self, name: &str) -> bool; + fn thread_new_indirect(&self, name: &str) -> bool; + fn thread_switch_to(&self, name: &str) -> Option>; + fn thread_suspend(&self, name: &str) -> Option>; + fn thread_resume_later(&self, name: &str) -> bool; + fn thread_yield_to(&self, name: &str) -> Option>; fn module_to_interface( &self, module: &str, resolve: &Resolve, items: &IndexMap, ) -> Result<(WorldKey, InterfaceId)>; - fn strip_post_return<'a>(&self, s: &'a str) -> Option<&'a str>; + fn strip_post_return<'a>(&self, name: &'a str) -> Option<&'a str>; fn match_wit_export<'a>( &self, export_name: &str, @@ -1512,6 +1625,8 @@ trait NameMangling { world: WorldId, exports: &'a IndexSet, ) -> Option; + fn world_key_name_and_abi<'a>(&self, name: &'a str) -> (&'a str, AbiVariant); + fn interface_function_name_and_abi<'a>(&self, name: &'a str) -> (&'a str, AbiVariant); } /// Definition of the "standard" naming scheme which currently starts with @@ -1539,78 +1654,189 @@ impl NameMangling for Standard { fn export_realloc(&self) -> &str { "_realloc" } - fn resource_drop_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_suffix("_drop") + fn export_indirect_function_table(&self) -> Option<&str> { + None + } + fn resource_drop_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_suffix("_drop") + } + fn resource_new_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_suffix("_new") + } + fn resource_rep_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_suffix("_rep") + } + fn task_return_name<'a>(&self, _name: &'a str) -> Option<&'a str> { + None + } + fn task_cancel(&self, _name: &str) -> bool { + false + } + fn backpressure_set(&self, _name: &str) -> bool { + false + } + fn waitable_set_new(&self, _name: &str) -> bool { + false + } + fn waitable_set_wait(&self, _name: &str) -> Option> { + None + } + fn waitable_set_poll(&self, _name: &str) -> Option> { + None + } + fn waitable_set_drop(&self, _name: &str) -> bool { + false + } + fn waitable_join(&self, _name: &str) -> bool { + false + } + fn thread_yield(&self, _name: &str) -> Option> { + None + } + fn subtask_drop(&self, _name: &str) -> bool { + false } - fn resource_new_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_suffix("_new") + fn subtask_cancel(&self, _name: &str) -> Option> { + None } - fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_suffix("_rep") + fn async_lift_callback_name<'a>(&self, _name: &'a str) -> Option<&'a str> { + None } - fn task_return_name<'a>(&self, s: &'a str) -> Option<&'a str> { - _ = s; + fn async_lift_name<'a>(&self, _name: &'a str) -> Option<&'a str> { None } - fn task_cancel(&self) -> Option<&str> { + fn async_lift_stackful_name<'a>(&self, _name: &'a str) -> Option<&'a str> { None } - fn backpressure_set(&self) -> Option<&str> { + fn error_context_new(&self, _name: &str) -> Option { None } - fn waitable_set_new(&self) -> Option<&str> { + fn error_context_debug_message(&self, _name: &str) -> Option { None } - fn waitable_set_wait(&self) -> Option<&str> { + fn error_context_drop(&self, _name: &str) -> bool { + false + } + fn context_get(&self, _name: &str) -> Option { None } - fn waitable_set_poll(&self) -> Option<&str> { + fn context_set(&self, _name: &str) -> Option { None } - fn waitable_set_drop(&self) -> Option<&str> { + fn thread_index(&self, _name: &str) -> bool { + false + } + fn thread_new_indirect(&self, _name: &str) -> bool { + false + } + fn thread_switch_to(&self, _name: &str) -> Option> { None } - fn waitable_join(&self) -> Option<&str> { + fn thread_suspend(&self, _name: &str) -> Option> { None } - fn yield_(&self) -> Option<&str> { + fn thread_resume_later(&self, _name: &str) -> bool { + false + } + fn thread_yield_to(&self, _name: &str) -> Option> { None } - fn subtask_drop(&self) -> Option<&str> { + fn future_new( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option { None } - fn subtask_cancel(&self) -> Option<&str> { + fn future_write( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { None } - fn async_lift_callback_name<'a>(&self, s: &'a str) -> Option<&'a str> { - _ = s; + fn future_read( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { None } - fn async_lower_name<'a>(&self, s: &'a str) -> Option<&'a str> { - _ = s; + fn future_cancel_write( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { None } - fn async_lift_name<'a>(&self, s: &'a str) -> Option<&'a str> { - _ = s; + fn future_cancel_read( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { None } - fn async_lift_stackful_name<'a>(&self, s: &'a str) -> Option<&'a str> { - _ = s; + fn future_drop_writable( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option { None } - fn error_context_new(&self, _: &str) -> Option { + fn future_drop_readable( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option { None } - fn error_context_debug_message(&self, _: &str) -> Option { + fn stream_new( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option { None } - fn error_context_drop(&self) -> Option<&str> { + fn stream_write( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { + None + } + fn stream_read( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { + None + } + fn stream_cancel_write( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { None } - fn context_get(&self, _: &str) -> Option { + fn stream_cancel_read( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option> { None } - fn context_set(&self, _: &str) -> Option { + fn stream_drop_writable( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option { + None + } + fn stream_drop_readable( + &self, + _lookup_context: &PayloadLookupContext, + _name: &str, + ) -> Option { None } fn module_to_interface( @@ -1638,8 +1864,8 @@ impl NameMangling for Standard { } bail!("failed to find world item corresponding to interface `{interface}`") } - fn strip_post_return<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_suffix("_post") + fn strip_post_return<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_suffix("_post") } fn match_wit_export<'a>( &self, @@ -1677,6 +1903,13 @@ impl NameMangling for Standard { _ => None, } } + + fn world_key_name_and_abi<'a>(&self, name: &'a str) -> (&'a str, AbiVariant) { + (name, AbiVariant::GuestImport) + } + fn interface_function_name_and_abi<'a>(&self, name: &'a str) -> (&'a str, AbiVariant) { + (name, AbiVariant::GuestImport) + } } impl Standard { @@ -1720,6 +1953,107 @@ struct Legacy; const LEGACY: &'static dyn NameMangling = &Legacy; +impl Legacy { + // Looks for `[$prefix-N]foo` within `name`. If found then `foo` is + // used to find a function within `id` and `world` above. Once found + // then `N` is used to index within that function to extract a + // future/stream type. If that's all found then a `PayloadInfo` is + // returned to get attached to an intrinsic. + fn prefixed_payload( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + prefix: &str, + ) -> Option { + // parse the `prefix` into `func_name` and `type_index`, bailing out + // with `None` if anything doesn't match. + let (type_index, func_name) = prefixed_integer(name, prefix)?; + let type_index = type_index as usize; + + // Double-check that `func_name` is indeed a function name within + // this interface/world. Then additionally double-check that + // `type_index` is indeed a valid index for this function's type + // signature. + let function = get_function( + lookup_context.resolve, + lookup_context.world, + func_name, + lookup_context.id, + lookup_context.import, + ) + .ok()?; + let ty = *function + .find_futures_and_streams(lookup_context.resolve) + .get(type_index)?; + + // And if all that passes wrap up everything in a `PayloadInfo`. + Some(PayloadInfo { + name: name.to_string(), + ty, + function: function.name.clone(), + key: lookup_context + .key + .clone() + .unwrap_or_else(|| WorldKey::Name(name.to_string())), + interface: lookup_context.id, + imported: lookup_context.import, + }) + } + + fn maybe_async_lowered_payload( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + prefix: &str, + ) -> Option> { + let (async_lowered, clean_name) = self.strip_async_lowered_prefix(name); + let payload = self.prefixed_payload(lookup_context, clean_name, prefix)?; + Some(MaybeAsyncLowered { + inner: payload, + async_lowered, + }) + } + + fn strip_async_lowered_prefix<'a>(&self, name: &'a str) -> (bool, &'a str) { + name.strip_prefix("[async-lower]") + .map_or((false, name), |s| (true, s)) + } + fn match_with_async_lowered_prefix( + &self, + name: &str, + expected: &str, + ) -> Option> { + let (async_lowered, clean_name) = self.strip_async_lowered_prefix(name); + if clean_name == expected { + Some(MaybeAsyncLowered { + inner: (), + async_lowered, + }) + } else { + None + } + } + fn strip_cancellable_prefix<'a>(&self, name: &'a str) -> (bool, &'a str) { + name.strip_prefix("[cancellable]") + .map_or((false, name), |s| (true, s)) + } + fn match_with_cancellable_prefix( + &self, + name: &str, + expected: &str, + ) -> Option> { + let (cancellable, clean_name) = self.strip_cancellable_prefix(name); + if clean_name == expected { + Some(MaybeCancellable { + inner: (), + cancellable, + }) + } else { + None + } + } +} + impl NameMangling for Legacy { fn import_root(&self) -> &str { "$root" @@ -1739,59 +2073,59 @@ impl NameMangling for Legacy { fn export_realloc(&self) -> &str { "cabi_realloc" } - fn resource_drop_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[resource-drop]") + fn export_indirect_function_table(&self) -> Option<&str> { + Some("__indirect_function_table") } - fn resource_new_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[resource-new]") + fn resource_drop_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[resource-drop]") } - fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[resource-rep]") + fn resource_new_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[resource-new]") } - fn task_return_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[task-return]") + fn resource_rep_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[resource-rep]") } - fn task_cancel(&self) -> Option<&str> { - Some("[task-cancel]") + fn task_return_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[task-return]") } - fn backpressure_set(&self) -> Option<&str> { - Some("[backpressure-set]") + fn task_cancel(&self, name: &str) -> bool { + name == "[task-cancel]" } - fn waitable_set_new(&self) -> Option<&str> { - Some("[waitable-set-new]") + fn backpressure_set(&self, name: &str) -> bool { + name == "[backpressure-set]" } - fn waitable_set_wait(&self) -> Option<&str> { - Some("[waitable-set-wait]") + fn waitable_set_new(&self, name: &str) -> bool { + name == "[waitable-set-new]" } - fn waitable_set_poll(&self) -> Option<&str> { - Some("[waitable-set-poll]") + fn waitable_set_wait(&self, name: &str) -> Option> { + self.match_with_cancellable_prefix(name, "[waitable-set-wait]") } - fn waitable_set_drop(&self) -> Option<&str> { - Some("[waitable-set-drop]") + fn waitable_set_poll(&self, name: &str) -> Option> { + self.match_with_cancellable_prefix(name, "[waitable-set-poll]") } - fn waitable_join(&self) -> Option<&str> { - Some("[waitable-join]") + fn waitable_set_drop(&self, name: &str) -> bool { + name == "[waitable-set-drop]" } - fn yield_(&self) -> Option<&str> { - Some("[yield]") + fn waitable_join(&self, name: &str) -> bool { + name == "[waitable-join]" } - fn subtask_drop(&self) -> Option<&str> { - Some("[subtask-drop]") + fn thread_yield(&self, name: &str) -> Option> { + self.match_with_cancellable_prefix(name, "[thread-yield]") } - fn subtask_cancel(&self) -> Option<&str> { - Some("[subtask-cancel]") + fn subtask_drop(&self, name: &str) -> bool { + name == "[subtask-drop]" } - fn async_lift_callback_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[callback][async-lift]") + fn subtask_cancel(&self, name: &str) -> Option> { + self.match_with_async_lowered_prefix(name, "[subtask-cancel]") } - fn async_lower_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[async-lower]") + fn async_lift_callback_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[callback][async-lift]") } - fn async_lift_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[async-lift]") + fn async_lift_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[async-lift]") } - fn async_lift_stackful_name<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("[async-lift-stackful]") + fn async_lift_stackful_name<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("[async-lift-stackful]") } fn error_context_new(&self, name: &str) -> Option { match name { @@ -1809,8 +2143,8 @@ impl NameMangling for Legacy { _ => None, } } - fn error_context_drop(&self) -> Option<&str> { - Some("[error-context-drop]") + fn error_context_drop(&self, name: &str) -> bool { + name == "[error-context-drop]" } fn context_get(&self, name: &str) -> Option { let (n, rest) = prefixed_integer(name, "[context-get-")?; @@ -1820,6 +2154,115 @@ impl NameMangling for Legacy { let (n, rest) = prefixed_integer(name, "[context-set-")?; if rest.is_empty() { Some(n) } else { None } } + fn thread_index(&self, name: &str) -> bool { + name == "[thread-index]" + } + fn thread_new_indirect(&self, name: &str) -> bool { + // For now, we'll fix the type of the start function and the table to extract it from + name == "[thread-new-indirect-v0]" + } + fn thread_switch_to(&self, name: &str) -> Option> { + self.match_with_cancellable_prefix(name, "[thread-switch-to]") + } + fn thread_suspend(&self, name: &str) -> Option> { + self.match_with_cancellable_prefix(name, "[thread-suspend]") + } + fn thread_resume_later(&self, name: &str) -> bool { + name == "[thread-resume-later]" + } + fn thread_yield_to(&self, name: &str) -> Option> { + self.match_with_cancellable_prefix(name, "[thread-yield-to]") + } + fn future_new(&self, lookup_context: &PayloadLookupContext, name: &str) -> Option { + self.prefixed_payload(lookup_context, name, "[future-new-") + } + fn future_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[future-write-") + } + fn future_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[future-read-") + } + fn future_cancel_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[future-cancel-write-") + } + fn future_cancel_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[future-cancel-read-") + } + fn future_drop_writable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option { + self.prefixed_payload(lookup_context, name, "[future-drop-writable-") + } + fn future_drop_readable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option { + self.prefixed_payload(lookup_context, name, "[future-drop-readable-") + } + fn stream_new(&self, lookup_context: &PayloadLookupContext, name: &str) -> Option { + self.prefixed_payload(lookup_context, name, "[stream-new-") + } + fn stream_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[stream-write-") + } + fn stream_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[stream-read-") + } + fn stream_cancel_write( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[stream-cancel-write-") + } + fn stream_cancel_read( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option> { + self.maybe_async_lowered_payload(lookup_context, name, "[stream-cancel-read-") + } + fn stream_drop_writable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option { + self.prefixed_payload(lookup_context, name, "[stream-drop-writable-") + } + fn stream_drop_readable( + &self, + lookup_context: &PayloadLookupContext, + name: &str, + ) -> Option { + self.prefixed_payload(lookup_context, name, "[stream-drop-readable-") + } fn module_to_interface( &self, module: &str, @@ -1901,8 +2344,8 @@ impl NameMangling for Legacy { bail!("module requires an import interface named `{module}`") } - fn strip_post_return<'a>(&self, s: &'a str) -> Option<&'a str> { - s.strip_prefix("cabi_post_") + fn strip_post_return<'a>(&self, name: &'a str) -> Option<&'a str> { + name.strip_prefix("cabi_post_") } fn match_wit_export<'a>( &self, @@ -1969,6 +2412,29 @@ impl NameMangling for Legacy { None } + + fn world_key_name_and_abi<'a>(&self, name: &'a str) -> (&'a str, AbiVariant) { + let (async_abi, name) = self.strip_async_lowered_prefix(name); + ( + name, + if async_abi { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + ) + } + fn interface_function_name_and_abi<'a>(&self, name: &'a str) -> (&'a str, AbiVariant) { + let (async_abi, name) = self.strip_async_lowered_prefix(name); + ( + name, + if async_abi { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + ) + } } /// This function validates the following: diff --git a/crates/wit-component/tests/components/async-builtins/component.wat b/crates/wit-component/tests/components/async-builtins/component.wat index e84346ac41..e5c37d5217 100644 --- a/crates/wit-component/tests/components/async-builtins/component.wat +++ b/crates/wit-component/tests/components/async-builtins/component.wat @@ -16,7 +16,7 @@ (import "$root" "[waitable-set-poll]" (func (;6;) (type 4))) (import "$root" "[waitable-set-drop]" (func (;7;) (type 0))) (import "$root" "[waitable-join]" (func (;8;) (type 2))) - (import "$root" "[yield]" (func (;9;) (type 3))) + (import "$root" "[thread-yield]" (func (;9;) (type 3))) (import "$root" "[subtask-drop]" (func (;10;) (type 0))) (import "$root" "[subtask-cancel]" (func (;11;) (type 5))) (import "$root" "[error-context-new-utf8]" (func (;12;) (type 4))) @@ -154,7 +154,7 @@ (alias core export 0 "1" (core func (;3;))) (core func (;4;) (canon waitable-set.drop)) (core func (;5;) (canon waitable.join)) - (core func (;6;) (canon yield)) + (core func (;6;) (canon thread.yield)) (core func (;7;) (canon subtask.drop)) (core func (;8;) (canon subtask.cancel)) (alias core export 0 "2" (core func (;9;))) @@ -173,7 +173,7 @@ (export "[waitable-set-poll]" (func 3)) (export "[waitable-set-drop]" (func 4)) (export "[waitable-join]" (func 5)) - (export "[yield]" (func 6)) + (export "[thread-yield]" (func 6)) (export "[subtask-drop]" (func 7)) (export "[subtask-cancel]" (func 8)) (export "[error-context-new-utf8]" (func 9)) diff --git a/crates/wit-component/tests/components/async-builtins/module.wat b/crates/wit-component/tests/components/async-builtins/module.wat index adb7a582ff..9b0ee257d1 100644 --- a/crates/wit-component/tests/components/async-builtins/module.wat +++ b/crates/wit-component/tests/components/async-builtins/module.wat @@ -8,7 +8,7 @@ (import "$root" "[waitable-set-poll]" (func (param i32 i32) (result i32))) (import "$root" "[waitable-set-drop]" (func (param i32))) (import "$root" "[waitable-join]" (func (param i32 i32))) - (import "$root" "[yield]" (func (result i32))) + (import "$root" "[thread-yield]" (func (result i32))) (import "$root" "[subtask-drop]" (func (param i32))) (import "$root" "[subtask-cancel]" (func (param i32) (result i32))) (import "$root" "[error-context-new-utf8]" (func (param i32 i32) (result i32))) diff --git a/crates/wit-component/tests/components/threading/component.wat b/crates/wit-component/tests/components/threading/component.wat new file mode 100644 index 0000000000..6287883974 --- /dev/null +++ b/crates/wit-component/tests/components/threading/component.wat @@ -0,0 +1,163 @@ +(component + (core module (;0;) + (type $thread-start-func-ty (;0;) (func (param i32))) + (type (;1;) (func)) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (result i32))) + (type (;4;) (func (param i32 i32) (result i32))) + (type (;5;) (func (param i32) (result i32))) + (type (;6;) (func (param i32 i32 i32 i32) (result i32))) + (import "[export]$root" "[task-cancel]" (func (;0;) (type 1))) + (import "[export]$root" "[task-return]foo" (func (;1;) (type 2))) + (import "[export]foo:foo/bar" "[task-return]foo" (func (;2;) (type 2))) + (import "$root" "[context-get-1]" (func (;3;) (type 3))) + (import "$root" "[context-set-1]" (func (;4;) (type $thread-start-func-ty))) + (import "$root" "[thread-index]" (func (;5;) (type 3))) + (import "$root" "[thread-new-indirect-v0]" (func (;6;) (type 4))) + (import "$root" "[thread-switch-to]" (func (;7;) (type 5))) + (import "$root" "[thread-suspend]" (func (;8;) (type 3))) + (import "$root" "[thread-resume-later]" (func (;9;) (type $thread-start-func-ty))) + (import "$root" "[thread-yield-to]" (func (;10;) (type 5))) + (table (;0;) 1 1 funcref) + (memory (;0;) 1) + (export "thread_main" (func 11)) + (export "[async-lift-stackful]foo" (func 12)) + (export "[async-lift-stackful]foo:foo/bar#foo" (func 13)) + (export "memory" (memory 0)) + (export "__indirect_function_table" (table 0)) + (export "cabi_realloc" (func 14)) + (elem (;0;) (i32.const 0) func 0) + (func (;11;) (type $thread-start-func-ty) (param i32) + unreachable + ) + (func (;12;) (type 2) (param i32 i32) + unreachable + ) + (func (;13;) (type 2) (param i32 i32) + unreachable + ) + (func (;14;) (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))) + (type (;1;) (func (param i32 i32) (result i32))) + (table (;0;) 3 3 funcref) + (export "0" (func $task-return-foo)) + (export "1" (func $"#func1 task-return-foo")) + (export "2" (func $thread.new_indirect)) + (export "$imports" (table 0)) + (func $task-return-foo (;0;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 0 + call_indirect (type 0) + ) + (func $"#func1 task-return-foo" (@name "task-return-foo") (;1;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 1 + call_indirect (type 0) + ) + (func $thread.new_indirect (;2;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 2 + call_indirect (type 1) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;2;) + (type (;0;) (func (param i32 i32))) + (type (;1;) (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 "" "$imports" (table (;0;) 3 3 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 1)) + (core func (;0;) (canon task.cancel)) + (alias core export 0 "0" (core func (;1;))) + (core instance (;1;) + (export "[task-cancel]" (func 0)) + (export "[task-return]foo" (func 1)) + ) + (alias core export 0 "1" (core func (;2;))) + (core instance (;2;) + (export "[task-return]foo" (func 2)) + ) + (core func (;3;) (canon context.get i32 1)) + (core func (;4;) (canon context.set i32 1)) + (core func (;5;) (canon thread.index)) + (alias core export 0 "2" (core func (;6;))) + (core func (;7;) (canon thread.switch-to)) + (core func (;8;) (canon thread.suspend)) + (core func (;9;) (canon thread.resume-later)) + (core func (;10;) (canon thread.yield-to)) + (core instance (;3;) + (export "[context-get-1]" (func 3)) + (export "[context-set-1]" (func 4)) + (export "[thread-index]" (func 5)) + (export "[thread-new-indirect-v0]" (func 6)) + (export "[thread-switch-to]" (func 7)) + (export "[thread-suspend]" (func 8)) + (export "[thread-resume-later]" (func 9)) + (export "[thread-yield-to]" (func 10)) + ) + (core instance (;4;) (instantiate 0 + (with "[export]$root" (instance 1)) + (with "[export]foo:foo/bar" (instance 2)) + (with "$root" (instance 3)) + ) + ) + (alias core export 4 "memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (alias core export 4 "cabi_realloc" (core func (;11;))) + (core func (;12;) (canon task.return (result string) (memory 0) string-encoding=utf8)) + (core func (;13;) (canon task.return (result string) (memory 0) string-encoding=utf8)) + (core type (;0;) (func (param i32))) + (alias core export 4 "__indirect_function_table" (core table (;1;))) + (core func (;14;) (canon thread.new_indirect 0 (table 1))) + (core instance (;5;) + (export "$imports" (table 0)) + (export "0" (func 12)) + (export "1" (func 13)) + (export "2" (func 14)) + ) + (core instance (;6;) (instantiate 2 + (with "" (instance 5)) + ) + ) + (type (;0;) (func (param "s" string) (result string))) + (alias core export 4 "[async-lift-stackful]foo" (core func (;15;))) + (func (;0;) (type 0) (canon lift (core func 15) (memory 0) (realloc 11) string-encoding=utf8 async)) + (export (;1;) "foo" (func 0)) + (type (;1;) (func (param "s" string) (result string))) + (alias core export 4 "[async-lift-stackful]foo:foo/bar#foo" (core func (;16;))) + (func (;2;) (type 1) (canon lift (core func 16) (memory 0) (realloc 11) 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/threading/component.wit.print b/crates/wit-component/tests/components/threading/component.wit.print new file mode 100644 index 0000000000..8c6e69aa82 --- /dev/null +++ b/crates/wit-component/tests/components/threading/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/threading/module.wat b/crates/wit-component/tests/components/threading/module.wat new file mode 100644 index 0000000000..4b4099ed48 --- /dev/null +++ b/crates/wit-component/tests/components/threading/module.wat @@ -0,0 +1,22 @@ +(module + (type $thread-start-func-ty (func (param i32))) + (import "[export]$root" "[task-cancel]" (func)) + (import "[export]$root" "[task-return]foo" (func (param i32 i32))) + (import "[export]foo:foo/bar" "[task-return]foo" (func (param i32 i32))) + (import "$root" "[context-get-1]" (func (result i32))) + (import "$root" "[context-set-1]" (func (param i32))) + (import "$root" "[thread-index]" (func (result i32))) + (import "$root" "[thread-new-indirect-v0]" (func (param i32 i32) (result i32))) + (import "$root" "[thread-switch-to]" (func (param i32) (result i32))) + (import "$root" "[thread-suspend]" (func (result i32))) + (import "$root" "[thread-resume-later]" (func (param i32))) + (import "$root" "[thread-yield-to]" (func (param i32) (result i32))) + (func (export "thread_main") (param i32) unreachable) + (func (export "[async-lift-stackful]foo") (param i32 i32) unreachable) + (func (export "[async-lift-stackful]foo:foo/bar#foo") (param i32 i32) unreachable) + (memory (export "memory") 1) + (table (export "__indirect_function_table") 1 1 funcref) + (elem (i32.const 0) func 0) + + (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) diff --git a/crates/wit-component/tests/components/threading/module.wit b/crates/wit-component/tests/components/threading/module.wit new file mode 100644 index 0000000000..f0629ae1d2 --- /dev/null +++ b/crates/wit-component/tests/components/threading/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/src/bin/wasm-tools/dump.rs b/src/bin/wasm-tools/dump.rs index 42f81c2e88..4d5ff800e5 100644 --- a/src/bin/wasm-tools/dump.rs +++ b/src/bin/wasm-tools/dump.rs @@ -432,7 +432,7 @@ impl<'a> Dump<'a> { | CanonicalFunction::WaitableSetPoll { .. } | CanonicalFunction::WaitableSetDrop | CanonicalFunction::WaitableJoin - | CanonicalFunction::Yield { .. } + | CanonicalFunction::ThreadYield { .. } | CanonicalFunction::SubtaskDrop | CanonicalFunction::SubtaskCancel { .. } | CanonicalFunction::StreamNew { .. } @@ -451,7 +451,13 @@ impl<'a> Dump<'a> { | CanonicalFunction::FutureDropWritable { .. } | CanonicalFunction::ErrorContextNew { .. } | CanonicalFunction::ErrorContextDebugMessage { .. } - | CanonicalFunction::ErrorContextDrop => { + | CanonicalFunction::ErrorContextDrop + | CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadNewIndirect { .. } + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater { .. } + | CanonicalFunction::ThreadYieldTo { .. } => { ("core func", &mut i.core_funcs) } }; diff --git a/tests/cli/component-model-async/task-builtins.wast b/tests/cli/component-model-async/task-builtins.wast index 47795fe36a..28fb3a99fe 100644 --- a/tests/cli/component-model-async/task-builtins.wast +++ b/tests/cli/component-model-async/task-builtins.wast @@ -102,7 +102,7 @@ (core module $m (import "" "waitable-set.wait" (func $waitable-set-wait (param i32 i32) (result i32))) ) - (core func $waitable-set-wait (canon waitable-set.wait async (memory $libc "memory"))) + (core func $waitable-set-wait (canon waitable-set.wait cancellable (memory $libc "memory"))) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) ) @@ -114,7 +114,7 @@ (core module $m (import "" "waitable-set.wait" (func $waitable-set-wait (param i32) (result i32))) ) - (core func $waitable-set-wait (canon waitable-set.wait async (memory $libc "memory"))) + (core func $waitable-set-wait (canon waitable-set.wait cancellable (memory $libc "memory"))) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) ) "type mismatch for export `waitable-set.wait` of module instantiation argument ``" @@ -127,7 +127,7 @@ (core module $m (import "" "waitable-set.poll" (func $waitable-set-poll (param i32 i32) (result i32))) ) - (core func $waitable-set-poll (canon waitable-set.poll async (memory $libc "memory"))) + (core func $waitable-set-poll (canon waitable-set.poll cancellable (memory $libc "memory"))) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) ) @@ -139,7 +139,7 @@ (core module $m (import "" "waitable-set.poll" (func $waitable-set-poll (param i32) (result i32))) ) - (core func $waitable-set-poll (canon waitable-set.poll async (memory $libc "memory"))) + (core func $waitable-set-poll (canon waitable-set.poll cancellable (memory $libc "memory"))) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) ) "type mismatch for export `waitable-set.poll` of module instantiation argument ``" @@ -179,25 +179,25 @@ "type mismatch for export `waitable.join` of module instantiation argument ``" ) -;; yield +;; thread.yield (component (core module $m - (import "" "yield" (func $yield (result i32))) + (import "" "thread.yield" (func $thread.yield (result i32))) ) - (core func $yield (canon yield async)) - (core instance $i (instantiate $m (with "" (instance (export "yield" (func $yield)))))) + (core func $thread.yield (canon thread.yield cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) ) -;; yield; incorrect type +;; thread.yield; incorrect type (assert_invalid (component (core module $m - (import "" "yield" (func $yield (param i32) (result i32))) + (import "" "thread.yield" (func $thread.yield (param i32) (result i32))) ) - (core func $yield (canon yield async)) - (core instance $i (instantiate $m (with "" (instance (export "yield" (func $yield)))))) + (core func $thread.yield (canon thread.yield cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) ) - "type mismatch for export `yield` of module instantiation argument ``" + "type mismatch for export `thread.yield` of module instantiation argument ``" ) ;; subtask.drop @@ -381,9 +381,11 @@ (core func (canon context.get i32 0)) (canon context.get i32 0 (core func)) - (core func (canon context.set i32 0)) (canon context.set i32 0 (core func)) + + (core func (canon thread.yield)) + (canon thread.yield (core func)) ) (component diff --git a/tests/cli/component-model-async/threading.wast b/tests/cli/component-model-async/threading.wast new file mode 100644 index 0000000000..744f513755 --- /dev/null +++ b/tests/cli/component-model-async/threading.wast @@ -0,0 +1,175 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async,cm-threading,cm-error-context + +;; context.{get,set} 1 +(component + (core func $get1 (canon context.get i32 1)) + (core func $set1 (canon context.set i32 1)) + + (core module $m + (import "" "get1" (func (result i32))) + (import "" "set1" (func (param i32))) + ) + (core instance (instantiate $m + (with "" (instance + (export "get1" (func $get1)) + (export "set1" (func $set1)) + )) + )) +) + +(assert_invalid + (component + (core func (canon context.get i32 2))) + "immediate must be zero or one: 2") +(assert_invalid + (component + (core func (canon context.set i32 2))) + "immediate must be zero or one: 2") +(assert_invalid + (component + (core func (canon context.get i32 100))) + "immediate must be zero or one: 100") +(assert_invalid + (component + (core func (canon context.set i32 100))) + "immediate must be zero or one: 100") + +(assert_invalid + (component + (core module $m (import "" "" (func (param i32) (result i32)))) + (core func $f (canon context.get i32 1)) + (core instance $i (instantiate $m (with "" (instance (export "" (func $f)))))) + ) + "found: (func (result i32))") +(assert_invalid + (component + (core module $m (import "" "" (func (param i32) (result i32)))) + (core func $f (canon context.set i32 1)) + (core instance $i (instantiate $m (with "" (instance (export "" (func $f)))))) + ) + "found: (func (param i32))") + +;; thread.new_indirect +(component + (core type $start (func (param $context i32))) + (core module $libc (table (export "start-table") 1 (ref null func))) + (core instance $libc (instantiate $libc)) + (core func $new_indirect (canon thread.new_indirect $start (table $libc "start-table"))) +) + +(component + (core type $start (func (param $context i32))) + (core module $libc (table (export "start-table") 1 (ref null func))) + (core instance $libc (instantiate $libc)) + (core func $new_indirect (canon thread.new_indirect $start (table $libc "start-table"))) + + (core module $m + (type $new_indirect_ty (func (param i32) (param i32) (result i32))) + (import "" "thread.new_indirect" (func (type $new_indirect_ty))) + ) + + (core instance (instantiate $m + (with "" (instance + (export "thread.new_indirect" (func $new_indirect)) + )) + )) +) + +(assert_invalid + (component + (core type $start (func (param i32))) + ;; Refer to a non-existent table type (i.e., 0); validation + ;; for `thread.new_indirect` happens first. + (core func $new_indirect (canon thread.new_indirect $start (table 0))) + ) + "unknown table 0: table index out of bounds" +) + +(assert_invalid + (component + (core type $start (func)) + (core module $libc (table (export "start-table") 1 (ref null func))) + (core instance $libc (instantiate $libc)) + (core func $new_indirect (canon thread.new_indirect $start (table $libc "start-table"))) + ) + "start function must take a single `i32` argument" +) + +;; thead.index +(component + (core module $m + (import "" "thread.index" (func $thread.index (result i32))) + ) + (core func $thread.index (canon thread.index)) + (core instance $i (instantiate $m (with "" (instance (export "thread.index" (func $thread.index)))))) +) + +;; thread.index; incorrect type +(assert_invalid + (component + (core module $m + (import "" "thread.index" (func $thread.index (param i32) (result i32))) + ) + (core func $thread.index (canon thread.index)) + (core instance $i (instantiate $m (with "" (instance (export "thread.index" (func $thread.index)))))) + ) + "type mismatch for export `thread.index` of module instantiation argument ``" +) + +;; thread.switch-to +(component + (core module $m + (import "" "thread.switch-to" (func $thread.switch-to (param i32) (result i32))) + ) + (core func $thread.switch-to (canon thread.switch-to cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.switch-to" (func $thread.switch-to)))))) +) + +;; thread.switch-to; incorrect type +(assert_invalid + (component + (core module $m + (import "" "thread.switch-to" (func $thread.switch-to (param i32))) + ) + (core func $thread.switch-to (canon thread.switch-to cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.switch-to" (func $thread.switch-to)))))) + ) + "type mismatch for export `thread.switch-to` of module instantiation argument ``" +) + +;; different forms of canonical intrinsics + +(component + (core module $m + (table (export "start-table") 1 (ref null func)) + ) + (core instance $i (instantiate $m)) + (alias core export $i "start-table" (core table $start-table)) + + (core func (canon context.get i32 1)) + (canon context.get i32 1 (core func)) + (core func (canon context.set i32 1)) + (canon context.set i32 1 (core func)) + + (core type $start (func (param i32))) + (core func (canon thread.new_indirect $start (table $start-table))) + (canon thread.new_indirect $start (table $start-table) (core func)) + (core func (canon thread.switch-to)) + (canon thread.switch-to (core func)) + (core func (canon thread.switch-to cancellable)) + (canon thread.switch-to cancellable (core func)) + (core func (canon thread.suspend)) + (canon thread.suspend (core func)) + (core func (canon thread.suspend cancellable)) + (canon thread.suspend cancellable (core func)) + (core func (canon thread.resume-later)) + (canon thread.resume-later (core func)) + (core func (canon thread.yield-to)) + (canon thread.yield-to (core func)) + (core func (canon thread.yield-to cancellable)) + (canon thread.yield-to cancellable (core func)) +) + +(component + (canon task.return (result (stream u8)) (core func)) +) diff --git a/tests/cli/missing-features/async-builtins.wast b/tests/cli/missing-features/component-model/async-builtins.wast similarity index 99% rename from tests/cli/missing-features/async-builtins.wast rename to tests/cli/missing-features/component-model/async-builtins.wast index ec4d52b4c7..e39c9855ef 100644 --- a/tests/cli/missing-features/async-builtins.wast +++ b/tests/cli/missing-features/component-model/async-builtins.wast @@ -41,3 +41,4 @@ (component (type $t (resource (rep i32))) (core func (canon resource.drop $t))) + \ No newline at end of file diff --git a/tests/cli/missing-features/async-stackful.wast b/tests/cli/missing-features/component-model/async-stackful.wast similarity index 74% rename from tests/cli/missing-features/async-stackful.wast rename to tests/cli/missing-features/component-model/async-stackful.wast index cf800e4c14..df9f1ae2e8 100644 --- a/tests/cli/missing-features/async-stackful.wast +++ b/tests/cli/missing-features/component-model/async-stackful.wast @@ -10,14 +10,14 @@ (canon lift (core func $i "foo") async) ) ) - "requires the async stackful feature") + "requires the component model async stackful feature") -;; waitable-set.wait async +;; waitable-set.wait cancellable (assert_invalid (component (core module $libc (memory (export "memory") 1)) (core instance $libc (instantiate $libc)) - (core func (canon waitable-set.wait async (memory $libc "memory"))) + (core func (canon waitable-set.wait cancellable (memory $libc "memory"))) ) "requires the component model async stackful feature") @@ -27,12 +27,12 @@ (core func (canon waitable-set.wait (memory $libc "memory"))) ) -;; waitable-set.poll async +;; waitable-set.poll cancellable (assert_invalid (component (core module $libc (memory (export "memory") 1)) (core instance $libc (instantiate $libc)) - (core func (canon waitable-set.poll async (memory $libc "memory"))) + (core func (canon waitable-set.poll cancellable (memory $libc "memory"))) ) "requires the component model async stackful feature") @@ -42,10 +42,9 @@ (core func (canon waitable-set.poll (memory $libc "memory"))) ) -;; yield +;; thread.yield (assert_invalid - (component (core func (canon yield async))) + (component (core func (canon thread.yield cancellable))) "requires the component model async stackful feature") -(component (core func (canon yield))) - +(component (core func (canon thread.yield))) \ No newline at end of file diff --git a/tests/cli/missing-features/component-model/async.wast b/tests/cli/missing-features/component-model/async.wast index f12c644c0a..538694646b 100644 --- a/tests/cli/missing-features/component-model/async.wast +++ b/tests/cli/missing-features/component-model/async.wast @@ -80,7 +80,7 @@ (core module $m (import "" "waitable-set.wait" (func $waitable-set-wait (param i32) (result i32))) ) - (core func $waitable-set-wait (canon waitable-set.wait async (memory $libc "memory"))) + (core func $waitable-set-wait (canon waitable-set.wait (memory $libc "memory"))) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) ) "`waitable-set.wait` requires the component model async feature" @@ -94,7 +94,7 @@ (core module $m (import "" "waitable-set.poll" (func $waitable-set-poll (param i32) (result i32))) ) - (core func $waitable-set-poll (canon waitable-set.poll async (memory $libc "memory"))) + (core func $waitable-set-poll (canon waitable-set.poll (memory $libc "memory"))) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) ) "`waitable-set.poll` requires the component model async feature" @@ -112,18 +112,6 @@ "`waitable.join` requires the component model async feature" ) -;; yield -(assert_invalid - (component - (core module $m - (import "" "yield" (func $yield)) - ) - (core func $yield (canon yield async)) - (core instance $i (instantiate $m (with "" (instance (export "yield" (func $yield)))))) - ) - "`yield` requires the component model async feature" -) - ;; subtask.drop (assert_invalid (component diff --git a/tests/cli/missing-features/component-model/threading.wast b/tests/cli/missing-features/component-model/threading.wast new file mode 100644 index 0000000000..0d0562fdc5 --- /dev/null +++ b/tests/cli/missing-features/component-model/threading.wast @@ -0,0 +1,13 @@ +;; RUN: wast % --assert default --snapshot tests/snapshots \ +;; -f=cm-async,-cm-threading + +;; thread.* +(assert_invalid + (component + (core func (canon thread.index)) + (core func (canon thread.new_indirect 0 (table 0))) + (core func (canon thread.switch-to)) + (core func (canon thread.suspend)) + (core func (canon thread.resume-later)) + (core func (canon thread.yield-to))) + "requires the component model threading feature") \ No newline at end of file diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr index 8b93bad803..a07287ad9f 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, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-async-builtins, cm-error-context, cm-fixed-size-list, cm-gc, call-indirect-overlong, bulk-memory-opt, mvp, wasm1, wasm2, wasm3, lime1, 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, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-async-builtins, cm-threading, cm-error-context, cm-fixed-size-list, cm-gc, call-indirect-overlong, bulk-memory-opt, mvp, wasm1, wasm2, wasm3, lime1, all For more information, try '--help'. diff --git a/tests/snapshots/cli/component-model-async/task-builtins.wast.json b/tests/snapshots/cli/component-model-async/task-builtins.wast.json index 50464cc6dc..b3e68812da 100644 --- a/tests/snapshots/cli/component-model-async/task-builtins.wast.json +++ b/tests/snapshots/cli/component-model-async/task-builtins.wast.json @@ -136,7 +136,7 @@ "line": 193, "filename": "task-builtins.20.wasm", "module_type": "binary", - "text": "type mismatch for export `yield` of module instantiation argument ``" + "text": "type mismatch for export `thread.yield` of module instantiation argument ``" }, { "type": "module", @@ -248,7 +248,7 @@ }, { "type": "module", - "line": 389, + "line": 391, "filename": "task-builtins.37.wasm", "module_type": "binary" } diff --git a/tests/snapshots/cli/component-model-async/task-builtins.wast/11.print b/tests/snapshots/cli/component-model-async/task-builtins.wast/11.print index 8835e4a135..30cc2c0977 100644 --- a/tests/snapshots/cli/component-model-async/task-builtins.wast/11.print +++ b/tests/snapshots/cli/component-model-async/task-builtins.wast/11.print @@ -9,7 +9,7 @@ (import "" "waitable-set.wait" (func $waitable-set-wait (;0;) (type 0))) ) (alias core export $libc "memory" (core memory (;0;))) - (core func $waitable-set-wait (;0;) (canon waitable-set.wait async (memory 0))) + (core func $waitable-set-wait (;0;) (canon waitable-set.wait cancellable (memory 0))) (core instance (;1;) (export "waitable-set.wait" (func $waitable-set-wait)) ) diff --git a/tests/snapshots/cli/component-model-async/task-builtins.wast/13.print b/tests/snapshots/cli/component-model-async/task-builtins.wast/13.print index 98bca2b0c0..4690399a01 100644 --- a/tests/snapshots/cli/component-model-async/task-builtins.wast/13.print +++ b/tests/snapshots/cli/component-model-async/task-builtins.wast/13.print @@ -9,7 +9,7 @@ (import "" "waitable-set.poll" (func $waitable-set-poll (;0;) (type 0))) ) (alias core export $libc "memory" (core memory (;0;))) - (core func $waitable-set-poll (;0;) (canon waitable-set.poll async (memory 0))) + (core func $waitable-set-poll (;0;) (canon waitable-set.poll cancellable (memory 0))) (core instance (;1;) (export "waitable-set.poll" (func $waitable-set-poll)) ) diff --git a/tests/snapshots/cli/component-model-async/task-builtins.wast/19.print b/tests/snapshots/cli/component-model-async/task-builtins.wast/19.print index d99f410bf0..f8845497a5 100644 --- a/tests/snapshots/cli/component-model-async/task-builtins.wast/19.print +++ b/tests/snapshots/cli/component-model-async/task-builtins.wast/19.print @@ -1,11 +1,11 @@ (component (core module $m (;0;) (type (;0;) (func (result i32))) - (import "" "yield" (func $yield (;0;) (type 0))) + (import "" "thread.yield" (func $thread.yield (;0;) (type 0))) ) - (core func $yield (;0;) (canon yield async)) + (core func $thread.yield (;0;) (canon thread.yield cancellable)) (core instance (;0;) - (export "yield" (func $yield)) + (export "thread.yield" (func $thread.yield)) ) (core instance $i (;1;) (instantiate $m (with "" (instance 0)) diff --git a/tests/snapshots/cli/component-model-async/task-builtins.wast/36.print b/tests/snapshots/cli/component-model-async/task-builtins.wast/36.print index 216e7af76f..3ffca107b6 100644 --- a/tests/snapshots/cli/component-model-async/task-builtins.wast/36.print +++ b/tests/snapshots/cli/component-model-async/task-builtins.wast/36.print @@ -66,4 +66,6 @@ (core func (;50;) (canon context.get i32 0)) (core func (;51;) (canon context.set i32 0)) (core func (;52;) (canon context.set i32 0)) + (core func (;53;) (canon thread.yield)) + (core func (;54;) (canon thread.yield)) ) diff --git a/tests/snapshots/cli/component-model-async/threading.wast.json b/tests/snapshots/cli/component-model-async/threading.wast.json new file mode 100644 index 0000000000..90307605ad --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast.json @@ -0,0 +1,117 @@ +{ + "source_filename": "tests/cli/component-model-async/threading.wast", + "commands": [ + { + "type": "module", + "line": 4, + "filename": "threading.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 21, + "filename": "threading.1.wasm", + "module_type": "binary", + "text": "immediate must be zero or one: 2" + }, + { + "type": "assert_invalid", + "line": 25, + "filename": "threading.2.wasm", + "module_type": "binary", + "text": "immediate must be zero or one: 2" + }, + { + "type": "assert_invalid", + "line": 29, + "filename": "threading.3.wasm", + "module_type": "binary", + "text": "immediate must be zero or one: 100" + }, + { + "type": "assert_invalid", + "line": 33, + "filename": "threading.4.wasm", + "module_type": "binary", + "text": "immediate must be zero or one: 100" + }, + { + "type": "assert_invalid", + "line": 38, + "filename": "threading.5.wasm", + "module_type": "binary", + "text": "found: (func (result i32))" + }, + { + "type": "assert_invalid", + "line": 45, + "filename": "threading.6.wasm", + "module_type": "binary", + "text": "found: (func (param i32))" + }, + { + "type": "module", + "line": 53, + "filename": "threading.7.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 60, + "filename": "threading.8.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 79, + "filename": "threading.9.wasm", + "module_type": "binary", + "text": "unknown table 0: table index out of bounds" + }, + { + "type": "assert_invalid", + "line": 89, + "filename": "threading.10.wasm", + "module_type": "binary", + "text": "start function must take a single `i32` argument" + }, + { + "type": "module", + "line": 99, + "filename": "threading.11.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 109, + "filename": "threading.12.wasm", + "module_type": "binary", + "text": "type mismatch for export `thread.index` of module instantiation argument ``" + }, + { + "type": "module", + "line": 120, + "filename": "threading.13.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 130, + "filename": "threading.14.wasm", + "module_type": "binary", + "text": "type mismatch for export `thread.switch-to` of module instantiation argument ``" + }, + { + "type": "module", + "line": 142, + "filename": "threading.15.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 173, + "filename": "threading.16.wasm", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/cli/component-model-async/threading.wast/0.print b/tests/snapshots/cli/component-model-async/threading.wast/0.print new file mode 100644 index 0000000000..7f416aa032 --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/0.print @@ -0,0 +1,18 @@ +(component + (core func $get1 (;0;) (canon context.get i32 1)) + (core func $set1 (;1;) (canon context.set i32 1)) + (core module $m (;0;) + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (import "" "get1" (func (;0;) (type 0))) + (import "" "set1" (func (;1;) (type 1))) + ) + (core instance (;0;) + (export "get1" (func $get1)) + (export "set1" (func $set1)) + ) + (core instance (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/cli/component-model-async/threading.wast/11.print b/tests/snapshots/cli/component-model-async/threading.wast/11.print new file mode 100644 index 0000000000..d36b57ffea --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/11.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func (result i32))) + (import "" "thread.index" (func $thread.index (;0;) (type 0))) + ) + (core func $thread.index (;0;) (canon thread.index)) + (core instance (;0;) + (export "thread.index" (func $thread.index)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/cli/component-model-async/threading.wast/13.print b/tests/snapshots/cli/component-model-async/threading.wast/13.print new file mode 100644 index 0000000000..6c92cb7183 --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/13.print @@ -0,0 +1,14 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32) (result i32))) + (import "" "thread.switch-to" (func $thread.switch-to (;0;) (type 0))) + ) + (core func $thread.switch-to (;0;) (canon thread.switch-to cancellable)) + (core instance (;0;) + (export "thread.switch-to" (func $thread.switch-to)) + ) + (core instance $i (;1;) (instantiate $m + (with "" (instance 0)) + ) + ) +) diff --git a/tests/snapshots/cli/component-model-async/threading.wast/15.print b/tests/snapshots/cli/component-model-async/threading.wast/15.print new file mode 100644 index 0000000000..59adde4fb6 --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/15.print @@ -0,0 +1,29 @@ +(component + (core module $m (;0;) + (table (;0;) 1 funcref) + (export "start-table" (table 0)) + ) + (core instance $i (;0;) (instantiate $m)) + (alias core export $i "start-table" (core table $start-table (;0;))) + (core func (;0;) (canon context.get i32 1)) + (core func (;1;) (canon context.get i32 1)) + (core func (;2;) (canon context.set i32 1)) + (core func (;3;) (canon context.set i32 1)) + (core type $start (;0;) (func (param i32))) + (core func (;4;) (canon thread.new_indirect $start (table $start-table))) + (core func (;5;) (canon thread.new_indirect $start (table $start-table))) + (core func (;6;) (canon thread.switch-to)) + (core func (;7;) (canon thread.switch-to)) + (core func (;8;) (canon thread.switch-to cancellable)) + (core func (;9;) (canon thread.switch-to cancellable)) + (core func (;10;) (canon thread.suspend)) + (core func (;11;) (canon thread.suspend)) + (core func (;12;) (canon thread.suspend cancellable)) + (core func (;13;) (canon thread.suspend cancellable)) + (core func (;14;) (canon thread.resume-later)) + (core func (;15;) (canon thread.resume-later)) + (core func (;16;) (canon thread.yield-to)) + (core func (;17;) (canon thread.yield-to)) + (core func (;18;) (canon thread.yield-to cancellable)) + (core func (;19;) (canon thread.yield-to cancellable)) +) diff --git a/tests/snapshots/cli/component-model-async/threading.wast/16.print b/tests/snapshots/cli/component-model-async/threading.wast/16.print new file mode 100644 index 0000000000..28dfb3b000 --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/16.print @@ -0,0 +1,4 @@ +(component + (type (;0;) (stream u8)) + (core func (;0;) (canon task.return (result 0))) +) diff --git a/tests/snapshots/cli/component-model-async/threading.wast/7.print b/tests/snapshots/cli/component-model-async/threading.wast/7.print new file mode 100644 index 0000000000..60858a0d95 --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/7.print @@ -0,0 +1,10 @@ +(component + (core type $start (;0;) (func (param i32))) + (core module $libc (;0;) + (table (;0;) 1 funcref) + (export "start-table" (table 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (alias core export $libc "start-table" (core table (;0;))) + (core func $new_indirect (;0;) (canon thread.new_indirect $start (table 0))) +) diff --git a/tests/snapshots/cli/component-model-async/threading.wast/8.print b/tests/snapshots/cli/component-model-async/threading.wast/8.print new file mode 100644 index 0000000000..40cbf9610b --- /dev/null +++ b/tests/snapshots/cli/component-model-async/threading.wast/8.print @@ -0,0 +1,21 @@ +(component + (core type $start (;0;) (func (param i32))) + (core module $libc (;0;) + (table (;0;) 1 funcref) + (export "start-table" (table 0)) + ) + (core instance $libc (;0;) (instantiate $libc)) + (alias core export $libc "start-table" (core table (;0;))) + (core func $new_indirect (;0;) (canon thread.new_indirect $start (table 0))) + (core module $m (;1;) + (type $new_indirect_ty (;0;) (func (param i32 i32) (result i32))) + (import "" "thread.new_indirect" (func (;0;) (type $new_indirect_ty))) + ) + (core instance (;1;) + (export "thread.new_indirect" (func $new_indirect)) + ) + (core instance (;2;) (instantiate $m + (with "" (instance 1)) + ) + ) +) diff --git a/tests/snapshots/cli/missing-features/async-stackful.wast/6.print b/tests/snapshots/cli/missing-features/async-stackful.wast/6.print deleted file mode 100644 index ffbb762e32..0000000000 --- a/tests/snapshots/cli/missing-features/async-stackful.wast/6.print +++ /dev/null @@ -1,3 +0,0 @@ -(component - (core func (;0;) (canon yield)) -) diff --git a/tests/snapshots/cli/missing-features/async-builtins.wast.json b/tests/snapshots/cli/missing-features/component-model/async-builtins.wast.json similarity index 93% rename from tests/snapshots/cli/missing-features/async-builtins.wast.json rename to tests/snapshots/cli/missing-features/component-model/async-builtins.wast.json index 92de6d7617..e1105da089 100644 --- a/tests/snapshots/cli/missing-features/async-builtins.wast.json +++ b/tests/snapshots/cli/missing-features/component-model/async-builtins.wast.json @@ -1,5 +1,5 @@ { - "source_filename": "tests/cli/missing-features/async-builtins.wast", + "source_filename": "tests/cli/missing-features/component-model/async-builtins.wast", "commands": [ { "type": "assert_invalid", diff --git a/tests/snapshots/cli/missing-features/async-builtins.wast/4.print b/tests/snapshots/cli/missing-features/component-model/async-builtins.wast/4.print similarity index 100% rename from tests/snapshots/cli/missing-features/async-builtins.wast/4.print rename to tests/snapshots/cli/missing-features/component-model/async-builtins.wast/4.print diff --git a/tests/snapshots/cli/missing-features/async-builtins.wast/6.print b/tests/snapshots/cli/missing-features/component-model/async-builtins.wast/6.print similarity index 100% rename from tests/snapshots/cli/missing-features/async-builtins.wast/6.print rename to tests/snapshots/cli/missing-features/component-model/async-builtins.wast/6.print diff --git a/tests/snapshots/cli/missing-features/async-stackful.wast.json b/tests/snapshots/cli/missing-features/component-model/async-stackful.wast.json similarity index 88% rename from tests/snapshots/cli/missing-features/async-stackful.wast.json rename to tests/snapshots/cli/missing-features/component-model/async-stackful.wast.json index b12de4d023..0bf14ba56b 100644 --- a/tests/snapshots/cli/missing-features/async-stackful.wast.json +++ b/tests/snapshots/cli/missing-features/component-model/async-stackful.wast.json @@ -1,12 +1,12 @@ { - "source_filename": "tests/cli/missing-features/async-stackful.wast", + "source_filename": "tests/cli/missing-features/component-model/async-stackful.wast", "commands": [ { "type": "assert_invalid", "line": 5, "filename": "async-stackful.0.wasm", "module_type": "binary", - "text": "requires the async stackful feature" + "text": "requires the component model async stackful feature" }, { "type": "assert_invalid", diff --git a/tests/snapshots/cli/missing-features/async-stackful.wast/2.print b/tests/snapshots/cli/missing-features/component-model/async-stackful.wast/2.print similarity index 100% rename from tests/snapshots/cli/missing-features/async-stackful.wast/2.print rename to tests/snapshots/cli/missing-features/component-model/async-stackful.wast/2.print diff --git a/tests/snapshots/cli/missing-features/async-stackful.wast/4.print b/tests/snapshots/cli/missing-features/component-model/async-stackful.wast/4.print similarity index 100% rename from tests/snapshots/cli/missing-features/async-stackful.wast/4.print rename to tests/snapshots/cli/missing-features/component-model/async-stackful.wast/4.print diff --git a/tests/snapshots/cli/missing-features/component-model/async-stackful.wast/6.print b/tests/snapshots/cli/missing-features/component-model/async-stackful.wast/6.print new file mode 100644 index 0000000000..a1d9c7a10c --- /dev/null +++ b/tests/snapshots/cli/missing-features/component-model/async-stackful.wast/6.print @@ -0,0 +1,3 @@ +(component + (core func (;0;) (canon thread.yield)) +) diff --git a/tests/snapshots/cli/missing-features/component-model/async.wast.json b/tests/snapshots/cli/missing-features/component-model/async.wast.json index d65dd165a8..16ddc9dbd6 100644 --- a/tests/snapshots/cli/missing-features/component-model/async.wast.json +++ b/tests/snapshots/cli/missing-features/component-model/async.wast.json @@ -76,161 +76,154 @@ "line": 117, "filename": "async.10.wasm", "module_type": "binary", - "text": "`yield` requires the component model async feature" + "text": "`subtask.drop` requires the component model async feature" }, { "type": "assert_invalid", "line": 129, "filename": "async.11.wasm", "module_type": "binary", - "text": "`subtask.drop` requires the component model async feature" + "text": "`subtask.cancel` requires the component model async feature" }, { "type": "assert_invalid", "line": 141, "filename": "async.12.wasm", "module_type": "binary", - "text": "`subtask.cancel` requires the component model async feature" + "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 153, + "line": 154, "filename": "async.13.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 166, + "line": 169, "filename": "async.14.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 181, + "line": 184, "filename": "async.15.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 196, + "line": 197, "filename": "async.16.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 209, + "line": 210, "filename": "async.17.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 222, + "line": 223, "filename": "async.18.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 235, + "line": 236, "filename": "async.19.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 248, + "line": 249, "filename": "async.20.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 261, + "line": 264, "filename": "async.21.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 276, + "line": 279, "filename": "async.22.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 291, + "line": 292, "filename": "async.23.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 304, + "line": 305, "filename": "async.24.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 317, + "line": 318, "filename": "async.25.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 330, + "line": 331, "filename": "async.26.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 343, + "line": 335, "filename": "async.27.wasm", "module_type": "binary", "text": "requires the component model async feature" }, { "type": "assert_invalid", - "line": 347, + "line": 339, "filename": "async.28.wasm", "module_type": "binary", - "text": "requires the component model async feature" + "text": "requires the component model async builtins feature" }, { "type": "assert_invalid", - "line": 351, + "line": 347, "filename": "async.29.wasm", "module_type": "binary", - "text": "requires the component model async builtins feature" + "text": "require the component model async feature" }, { "type": "assert_invalid", - "line": 359, + "line": 352, "filename": "async.30.wasm", "module_type": "binary", "text": "require the component model async feature" }, { "type": "assert_invalid", - "line": 364, + "line": 357, "filename": "async.31.wasm", "module_type": "binary", "text": "require the component model async feature" - }, - { - "type": "assert_invalid", - "line": 369, - "filename": "async.32.wasm", - "module_type": "binary", - "text": "require the component model async feature" } ] } \ No newline at end of file diff --git a/tests/snapshots/cli/missing-features/component-model/threading.wast.json b/tests/snapshots/cli/missing-features/component-model/threading.wast.json new file mode 100644 index 0000000000..cf6e1c505c --- /dev/null +++ b/tests/snapshots/cli/missing-features/component-model/threading.wast.json @@ -0,0 +1,12 @@ +{ + "source_filename": "tests/cli/missing-features/component-model/threading.wast", + "commands": [ + { + "type": "assert_invalid", + "line": 6, + "filename": "threading.0.wasm", + "module_type": "binary", + "text": "requires the component model threading feature" + } + ] +} \ No newline at end of file