diff --git a/Cargo.lock b/Cargo.lock index 404df84276fe..6f443aaa4021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1306,7 +1306,7 @@ dependencies = [ name = "example-component-wasm" version = "0.0.0" dependencies = [ - "wit-bindgen 0.49.0", + "wit-bindgen 0.50.0", ] [[package]] @@ -1317,7 +1317,7 @@ version = "0.0.0" name = "example-resource-component-wasm" version = "0.1.0" dependencies = [ - "wit-bindgen 0.49.0", + "wit-bindgen 0.50.0", ] [[package]] @@ -3652,7 +3652,7 @@ dependencies = [ "wasi-nn", "wasip1", "wasip2", - "wit-bindgen 0.49.0", + "wit-bindgen 0.50.0", ] [[package]] @@ -5584,9 +5584,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64be1abfe5d4fdb2d41581fac134e8c9204da1e604b5500926478b7f264e36f" +checksum = "a3f72f96146c1e2648232350c3c59efe5a17738767f72efdc5e766b9e4a8c896" dependencies = [ "bitflags 2.9.4", "futures", @@ -5595,9 +5595,9 @@ dependencies = [ [[package]] name = "wit-bindgen-core" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886e8e938e4e9fe54143c080cbb99d7db5d19242b62ef225dbb28e17b3223bd8" +checksum = "6ee98d5747ec63897e51101c8cafad569cc639346332c0378772ef4baeea8ed2" dependencies = [ "anyhow", "heck 0.5.0", @@ -5615,9 +5615,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145cac8fb12d99aea13a3f9e0d07463fa030edeebab2c03805eda0e1cc229bba" +checksum = "8596d6a44bcc2b66e99213c342a85ff72be1109972f5353bb07d05a890a0c257" dependencies = [ "anyhow", "heck 0.5.0", @@ -5631,9 +5631,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6042452ac4e58891cdb6321bb98aabb9827dbaf6f4e971734d8dd86813319aea" +checksum = "098fd8b2c4bab0052159a87d02acad740a9c809afe9dcf5bd0b92eb3f63da154" dependencies = [ "anyhow", "prettyplease", diff --git a/Cargo.toml b/Cargo.toml index 8c3e9146afbc..891dd3ea3c3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -337,8 +337,8 @@ io-lifetimes = { version = "2.0.3", default-features = false } io-extras = "0.18.4" rustix = "1.0.8" # wit-bindgen: -wit-bindgen = { version = "0.49.0", default-features = false } -wit-bindgen-rust-macro = { version = "0.49.0", default-features = false } +wit-bindgen = { version = "0.50.0", default-features = false } +wit-bindgen-rust-macro = { version = "0.50.0", default-features = false } # wasm-tools family: wasmparser = { version = "0.243.0", default-features = false, features = ['simd'] } diff --git a/crates/test-programs/src/async_.rs b/crates/test-programs/src/async_.rs index 1ec58158d27d..61c59059e7d7 100644 --- a/crates/test-programs/src/async_.rs +++ b/crates/test-programs/src/async_.rs @@ -125,6 +125,17 @@ pub unsafe extern "C" fn task_cancel() { unreachable!() } +#[cfg(target_arch = "wasm32")] +#[link(wasm_import_module = "$root")] +unsafe extern "C" { + #[link_name = "[thread-yield]"] + pub fn thread_yield() -> u32; +} +#[cfg(not(target_arch = "wasm32"))] +pub unsafe fn thread_yield() -> u32 { + unreachable!() +} + pub const STATUS_STARTING: u32 = 0; pub const STATUS_STARTED: u32 = 1; pub const STATUS_RETURNED: u32 = 2; @@ -142,8 +153,10 @@ pub const EVENT_CANCELLED: u32 = 6; pub const CALLBACK_CODE_EXIT: u32 = 0; pub const CALLBACK_CODE_YIELD: u32 = 1; pub const CALLBACK_CODE_WAIT: u32 = 2; -pub const CALLBACK_CODE_POLL: u32 = 3; pub const BLOCKED: u32 = 0xffff_ffff; pub const DROPPED: u32 = 1; pub const COMPLETED: u32 = 0; + +pub const SUSPEND_RESULT_NOT_CANCELLED: u32 = 0; +pub const SUSPEND_RESULT_CANCELLED: u32 = 1; diff --git a/crates/test-programs/src/bin/async_poll_stackless.rs b/crates/test-programs/src/bin/async_poll_stackless.rs index 52c413f21bce..200cf479d126 100644 --- a/crates/test-programs/src/bin/async_poll_stackless.rs +++ b/crates/test-programs/src/bin/async_poll_stackless.rs @@ -10,8 +10,9 @@ mod bindings { use { bindings::local::local::ready, test_programs::async_::{ - CALLBACK_CODE_EXIT, CALLBACK_CODE_POLL, EVENT_NONE, EVENT_SUBTASK, STATUS_RETURNED, + CALLBACK_CODE_EXIT, CALLBACK_CODE_YIELD, EVENT_NONE, EVENT_SUBTASK, STATUS_RETURNED, context_get, context_set, subtask_drop, waitable_join, waitable_set_drop, waitable_set_new, + waitable_set_poll, }, }; @@ -59,7 +60,7 @@ unsafe extern "C" fn export_run() -> u32 { } #[unsafe(export_name = "[callback][async-lift]local:local/run#run")] -unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 { +unsafe extern "C" fn callback_run(event0: u32, _: u32, _: u32) -> u32 { let state = &mut *(usize::try_from(context_get()).unwrap() as *mut State); match state { State::S0 => { @@ -71,13 +72,14 @@ unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 *state = State::S1 { set }; - CALLBACK_CODE_POLL | (set << 4) + CALLBACK_CODE_YIELD } - State::S1 { set } => { + &mut State::S1 { set } => { + let (event0, _, _) = waitable_set_poll(set); + assert_eq!(event0, EVENT_NONE); - let set = *set; let result = async_when_ready(); let status = result & 0xf; let call = result >> 4; @@ -86,52 +88,55 @@ unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 *state = State::S2 { set, call }; - CALLBACK_CODE_POLL | (set << 4) + CALLBACK_CODE_YIELD } - State::S2 { set, call } => { + &mut State::S2 { set, call } => { + let (event0, _, _) = waitable_set_poll(set); + assert_eq!(event0, EVENT_NONE); - let set = *set; - let call = *call; ready::set_ready(true); *state = State::S3 { set, call }; - CALLBACK_CODE_POLL | (set << 4) + CALLBACK_CODE_YIELD } - State::S3 { set, call } => { - let set = *set; + &mut State::S3 { set, call } => { + let (event0, event1, event2) = waitable_set_poll(set); if event0 != EVENT_NONE { assert_eq!(event0, EVENT_SUBTASK); - assert_eq!(event1, *call); + assert_eq!(event1, call); assert_eq!(event2, STATUS_RETURNED); - subtask_drop(*call); + subtask_drop(call); *state = State::S4 { set }; } - CALLBACK_CODE_POLL | (set << 4) + CALLBACK_CODE_YIELD } - State::S4 { set } => { + &mut State::S4 { set } => { + let (event0, _, _) = waitable_set_poll(set); + assert_eq!(event0, EVENT_NONE); - let set = *set; assert_eq!(async_when_ready(), STATUS_RETURNED); *state = State::S5 { set }; - CALLBACK_CODE_POLL | (set << 4) + CALLBACK_CODE_YIELD } - State::S5 { set } => { + &mut State::S5 { set } => { + let (event0, _, _) = waitable_set_poll(set); + assert_eq!(event0, EVENT_NONE); - waitable_set_drop(*set); + waitable_set_drop(set); drop(Box::from_raw(state)); diff --git a/crates/test-programs/src/bin/async_poll_synchronous.rs b/crates/test-programs/src/bin/async_poll_synchronous.rs index 3a042028b5ac..9d5ed7f7cd38 100644 --- a/crates/test-programs/src/bin/async_poll_synchronous.rs +++ b/crates/test-programs/src/bin/async_poll_synchronous.rs @@ -12,8 +12,8 @@ mod bindings { use { bindings::{exports::local::local::run::Guest, local::local::ready}, test_programs::async_::{ - EVENT_NONE, EVENT_SUBTASK, STATUS_RETURNED, subtask_drop, waitable_join, waitable_set_drop, - waitable_set_new, waitable_set_poll, + EVENT_NONE, EVENT_SUBTASK, STATUS_RETURNED, SUSPEND_RESULT_NOT_CANCELLED, subtask_drop, + thread_yield, waitable_join, waitable_set_drop, waitable_set_new, waitable_set_poll, }, }; @@ -55,6 +55,8 @@ impl Guest for Component { ready::set_ready(true); + assert_eq!(thread_yield(), SUSPEND_RESULT_NOT_CANCELLED); + let (event, task, code) = waitable_set_poll(set); assert_eq!(event, EVENT_SUBTASK); assert_eq!(call, task); diff --git a/crates/test-util/src/wast.rs b/crates/test-util/src/wast.rs index f32eaa271ac2..65ed54fa0c46 100644 --- a/crates/test-util/src/wast.rs +++ b/crates/test-util/src/wast.rs @@ -701,8 +701,6 @@ impl WastTest { "component-model/test/values/trap-in-post-return.wast", "component-model/test/wasmtime/resources.wast", "component-model/test/wasm-tools/naming.wast", - // FIXME(#12129) - "component-model/test/async/trap-if-block-and-sync.wast", // TODO: Remove this once // https://github.com/bytecodealliance/wasm-tools/pull/2406 is // merged and released, and Wasmtime has been updated to use it: diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 4e4793763e7b..bd1a8b6f74dd 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -195,7 +195,6 @@ mod callback_code { pub const EXIT: u32 = 0; pub const YIELD: u32 = 1; pub const WAIT: u32 = 2; - pub const POLL: u32 = 3; } /// A flag indicating that the callee is an async-lowered export. @@ -674,18 +673,6 @@ enum WorkerItem { Function(AlwaysMut Result<()> + Send>>), } -/// Represents state related to an in-progress poll operation (e.g. `task.poll` -/// or `CallbackCode.POLL`). -#[derive(Debug)] -struct PollParams { - /// The instance to which the polling thread belongs. - instance: Instance, - /// The polling thread. - thread: QualifiedThreadId, - /// The waitable set being polled. - set: TableId, -} - /// Represents a pending work item to be handled by the event loop for a given /// component instance. enum WorkItem { @@ -695,8 +682,6 @@ enum WorkItem { ResumeFiber(StoreFiber<'static>), /// A pending call into guest code for a given guest task. GuestCall(GuestCall), - /// A pending `task.poll` or `CallbackCode.POLL` operation. - Poll(PollParams), /// A job to run on a worker fiber. WorkerFunction(AlwaysMut Result<()> + Send>>), } @@ -707,7 +692,6 @@ impl fmt::Debug for WorkItem { Self::PushFuture(_) => f.debug_tuple("PushFuture").finish(), Self::ResumeFiber(_) => f.debug_tuple("ResumeFiber").finish(), Self::GuestCall(call) => f.debug_tuple("GuestCall").field(call).finish(), - Self::Poll(params) => f.debug_tuple("Poll").field(params).finish(), Self::WorkerFunction(_) => f.debug_tuple("WorkerFunction").finish(), } } @@ -1345,33 +1329,6 @@ impl StoreContextMut<'_, T> { .insert(call.thread, call.kind); } } - WorkItem::Poll(params) => { - let state = self.0.concurrent_state_mut(); - if state.get_mut(params.thread.task)?.event.is_some() - || !state.get_mut(params.set)?.ready.is_empty() - { - // There's at least one event immediately available; deliver - // it to the guest ASAP. - state.push_high_priority(WorkItem::GuestCall(GuestCall { - thread: params.thread, - kind: GuestCallKind::DeliverEvent { - instance: params.instance, - set: Some(params.set), - }, - })); - } else { - // There are no events immediately available; deliver - // `Event::None` to the guest. - state.get_mut(params.thread.task)?.event = Some(Event::None); - state.push_high_priority(WorkItem::GuestCall(GuestCall { - thread: params.thread, - kind: GuestCallKind::DeliverEvent { - instance: params.instance, - set: Some(params.set), - }, - })); - } - } WorkItem::WorkerFunction(fun) => { self.run_on_worker(WorkerItem::Function(fun)).await?; } @@ -1802,9 +1759,9 @@ impl Instance { Some(call) } } - callback_code::WAIT | callback_code::POLL => { - // The task may only return `WAIT` or `POLL` if it was created - // for a call to an async export). Otherwise, we'll trap. + callback_code::WAIT => { + // The task may only return `WAIT` if it was created for a call + // to an async export). Otherwise, we'll trap. state.check_blocking_for(guest_thread.task)?; let set = get_set(store, set)?; @@ -1823,36 +1780,22 @@ impl Instance { })); } else { // No event is immediately available. - match code { - callback_code::POLL => { - // We're polling, so just yield and check whether an - // event has arrived after that. - state.push_low_priority(WorkItem::Poll(PollParams { - instance: self, - thread: guest_thread, - set, - })); - } - callback_code::WAIT => { - // We're waiting, so register to be woken up when an - // event is published for this waitable set. - // - // Here we also set `GuestTask::wake_on_cancel` - // which allows `subtask.cancel` to interrupt the - // wait. - let old = state - .get_mut(guest_thread.thread)? - .wake_on_cancel - .replace(set); - assert!(old.is_none()); - let old = state - .get_mut(set)? - .waiting - .insert(guest_thread, WaitMode::Callback(self)); - assert!(old.is_none()); - } - _ => unreachable!(), - } + // + // We're waiting, so register to be woken up when an event + // is published for this waitable set. + // + // Here we also set `GuestTask::wake_on_cancel` which allows + // `subtask.cancel` to interrupt the wait. + let old = state + .get_mut(guest_thread.thread)? + .wake_on_cancel + .replace(set); + assert!(old.is_none()); + let old = state + .get_mut(set)? + .waiting + .insert(guest_thread, WaitMode::Callback(self)); + assert!(old.is_none()); } None } @@ -3081,11 +3024,12 @@ impl Instance { self.waitable_check( store, cancellable, - WaitableCheck::Wait(WaitableCheckParams { + WaitableCheck::Wait, + WaitableCheckParams { set: TableId::new(rep), options, payload, - }), + }, ) } @@ -3100,13 +3044,6 @@ impl Instance { ) -> Result { self.id().get(store).check_may_leave(caller)?; - if !self.options(store, options).async_ { - // The caller may only call `waitable-set.poll` from an async task - // (i.e. a task created via a call to an async export). - // Otherwise, we'll trap. - store.concurrent_state_mut().check_blocking()?; - } - let &CanonicalOptions { cancellable, instance: caller_instance, @@ -3122,11 +3059,12 @@ impl Instance { self.waitable_check( store, cancellable, - WaitableCheck::Poll(WaitableCheckParams { + WaitableCheck::Poll, + WaitableCheckParams { set: TableId::new(rep), options, payload, - }), + }, ) } @@ -3353,91 +3291,83 @@ impl Instance { store: &mut StoreOpaque, cancellable: bool, check: WaitableCheck, + params: WaitableCheckParams, ) -> Result { let guest_thread = store.concurrent_state_mut().guest_thread.unwrap(); - let (wait, set) = match &check { - WaitableCheck::Wait(params) => (true, Some(params.set)), - WaitableCheck::Poll(params) => (false, Some(params.set)), - }; - - log::trace!("waitable check for {guest_thread:?}; set {set:?}"); - // First, suspend this fiber, allowing any other threads to run. - store.suspend(SuspendReason::Yielding { - thread: guest_thread, - })?; - - log::trace!("waitable check for {guest_thread:?}; set {set:?}"); + log::trace!("waitable check for {guest_thread:?}; set {:?}", params.set); let state = store.concurrent_state_mut(); let task = state.get_mut(guest_thread.task)?; // If we're waiting, and there are no events immediately available, // suspend the fiber until that changes. - if wait { - let set = set.unwrap(); + match &check { + WaitableCheck::Wait => { + let set = params.set; - if (task.event.is_none() - || (matches!(task.event, Some(Event::Cancelled)) && !cancellable)) - && state.get_mut(set)?.ready.is_empty() - { - if cancellable { - let old = state - .get_mut(guest_thread.thread)? - .wake_on_cancel - .replace(set); - assert!(old.is_none()); - } + if (task.event.is_none() + || (matches!(task.event, Some(Event::Cancelled)) && !cancellable)) + && state.get_mut(set)?.ready.is_empty() + { + if cancellable { + let old = state + .get_mut(guest_thread.thread)? + .wake_on_cancel + .replace(set); + assert!(old.is_none()); + } - store.suspend(SuspendReason::Waiting { - set, - thread: guest_thread, - skip_may_block_check: false, - })?; + store.suspend(SuspendReason::Waiting { + set, + thread: guest_thread, + skip_may_block_check: false, + })?; + } } + WaitableCheck::Poll => {} } - log::trace!("waitable check for {guest_thread:?}; set {set:?}, part two"); + log::trace!( + "waitable check for {guest_thread:?}; set {:?}, part two", + params.set + ); - let result = match check { - // Deliver any pending events to the guest and return. - WaitableCheck::Wait(params) | WaitableCheck::Poll(params) => { - let event = - self.get_event(store, guest_thread.task, Some(params.set), cancellable)?; + // Deliver any pending events to the guest and return. + let event = self.get_event(store, guest_thread.task, Some(params.set), cancellable)?; - let (ordinal, handle, result) = if wait { - let (event, waitable) = event.unwrap(); + let (ordinal, handle, result) = match &check { + WaitableCheck::Wait => { + let (event, waitable) = event.unwrap(); + let handle = waitable.map(|(_, v)| v).unwrap_or(0); + let (ordinal, result) = event.parts(); + (ordinal, handle, result) + } + WaitableCheck::Poll => { + if let Some((event, waitable)) = event { let handle = waitable.map(|(_, v)| v).unwrap_or(0); let (ordinal, result) = event.parts(); (ordinal, handle, result) } else { - if let Some((event, waitable)) = event { - let handle = waitable.map(|(_, v)| v).unwrap_or(0); - let (ordinal, result) = event.parts(); - (ordinal, handle, result) - } else { - log::trace!( - "no events ready to deliver via waitable-set.poll to {:?}; set {:?}", - guest_thread.task, - params.set - ); - let (ordinal, result) = Event::None.parts(); - (ordinal, 0, result) - } - }; - let memory = self.options_memory_mut(store, params.options); - let ptr = func::validate_inbounds_dynamic( - &CanonicalAbiInfo::POINTER_PAIR, - memory, - &ValRaw::u32(params.payload), - )?; - memory[ptr + 0..][..4].copy_from_slice(&handle.to_le_bytes()); - memory[ptr + 4..][..4].copy_from_slice(&result.to_le_bytes()); - Ok(ordinal) + log::trace!( + "no events ready to deliver via waitable-set.poll to {:?}; set {:?}", + guest_thread.task, + params.set + ); + let (ordinal, result) = Event::None.parts(); + (ordinal, 0, result) + } } }; - - result + let memory = self.options_memory_mut(store, params.options); + let ptr = func::validate_inbounds_dynamic( + &CanonicalAbiInfo::POINTER_PAIR, + memory, + &ValRaw::u32(params.payload), + )?; + memory[ptr + 0..][..4].copy_from_slice(&handle.to_le_bytes()); + memory[ptr + 4..][..4].copy_from_slice(&result.to_le_bytes()); + Ok(ordinal) } /// Implements the `subtask.cancel` intrinsic. @@ -5085,10 +5015,11 @@ struct WaitableCheckParams { payload: u32, } -/// Helper enum for passing parameters to `ComponentInstance::waitable_check`. +/// Indicates whether `ComponentInstance::waitable_check` is being called for +/// `waitable-set.wait` or `waitable-set.poll`. enum WaitableCheck { - Wait(WaitableCheckParams), - Poll(WaitableCheckParams), + Wait, + Poll, } /// Represents a guest task called from the host, prepared using `prepare_call`. diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 78d52757428c..4e339a4791e0 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -5359,6 +5359,12 @@ criteria = "safe-to-deploy" delta = "0.48.0 -> 0.49.0" notes = "The Bytecode Alliance is the author of this crate" +[[audits.wit-bindgen]] +who = "Joel Dice " +criteria = "safe-to-deploy" +delta = "0.49.0 -> 0.50.0" +notes = "The Bytecode Alliance is the author of this crate" + [[audits.wit-bindgen-core]] who = "Joel Dice " criteria = "safe-to-run" @@ -5394,6 +5400,12 @@ criteria = "safe-to-deploy" delta = "0.48.0 -> 0.49.0" notes = "The Bytecode Alliance is the author of this crate" +[[audits.wit-bindgen-core]] +who = "Joel Dice " +criteria = "safe-to-deploy" +delta = "0.49.0 -> 0.50.0" +notes = "The Bytecode Alliance is the author of this crate" + [[audits.wit-bindgen-rt]] who = "Joel Dice " criteria = "safe-to-run" @@ -5434,6 +5446,12 @@ criteria = "safe-to-deploy" delta = "0.48.0 -> 0.49.0" notes = "The Bytecode Alliance is the author of this crate" +[[audits.wit-bindgen-rust]] +who = "Joel Dice " +criteria = "safe-to-deploy" +delta = "0.49.0 -> 0.50.0" +notes = "The Bytecode Alliance is the author of this crate" + [[audits.wit-bindgen-rust-macro]] who = "Joel Dice " criteria = "safe-to-run" @@ -5469,6 +5487,12 @@ criteria = "safe-to-deploy" delta = "0.48.0 -> 0.49.0" notes = "The Bytecode Alliance is the author of this crate" +[[audits.wit-bindgen-rust-macro]] +who = "Joel Dice " +criteria = "safe-to-deploy" +delta = "0.49.0 -> 0.50.0" +notes = "The Bytecode Alliance is the author of this crate" + [[audits.wit-component]] who = "Alex Crichton " criteria = "safe-to-deploy" diff --git a/tests/misc_testsuite/component-model/async/trap-if-block-and-sync.wast b/tests/misc_testsuite/component-model/async/trap-if-block-and-sync.wast deleted file mode 100644 index 9abf5b26ea2d..000000000000 --- a/tests/misc_testsuite/component-model/async/trap-if-block-and-sync.wast +++ /dev/null @@ -1,377 +0,0 @@ -;;! component_model_async = true -;;! component_model_threading = true -;;! reference_types = true -;;! gc_types = true -;;! multi_memory = true - -;; TODO: remove this in favor of the upstream version once -;; https://github.com/WebAssembly/component-model/pull/578 has been merged. - -;; The $Tester component has two nested components $C and $D, where $D imports -;; and calls $C. $C contains utilities used by $D to perform all the tests. -;; Most of the tests trap, $Tester exports 1 function per test and a fresh -;; $Tester is created to run each test. -(component definition $Tester - (component $C - (core module $Memory (memory (export "mem") 1)) - (core instance $memory (instantiate $Memory)) - (core module $CM - (import "" "mem" (memory 1)) - (import "" "waitable.join" (func $waitable.join (param i32 i32))) - (import "" "waitable-set.new" (func $waitable-set.new (result i32))) - (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32))) - (import "" "future.new" (func $future.new (result i64))) - (import "" "future.read" (func $future.read (param i32 i32) (result i32))) - (import "" "future.write" (func $future.write (param i32 i32) (result i32))) - - ;; $ws is waited on by 'blocker' and added to by 'unblocker' - (global $ws (mut i32) (i32.const 0)) - (func $start (global.set $ws (call $waitable-set.new))) - (start $start) - - (func (export "blocker") (result i32) - ;; wait on $ws, which is initially empty, but will be populated with - ;; a completed future when "unblocker" synchronously barges in. - (local $ret i32) - (local.set $ret (call $waitable-set.wait (global.get $ws) (i32.const 0))) - (if (i32.ne (i32.const 4 (; FUTURE_READ ;)) (local.get $ret)) - (then unreachable)) - (if (i32.ne (i32.const 0 (; COMPLETED ;)) (i32.load (i32.const 4))) - (then unreachable)) - - (i32.const 42) - ) - - (func (export "unblocker") (result i32) - (local $ret i32) (local $ret64 i64) - (local $futr i32) (local $futw i32) - - ;; create read/write futures that will be used to unblock 'blocker' - (local.set $ret64 (call $future.new)) - (local.set $futr (i32.wrap_i64 (local.get $ret64))) - (local.set $futw (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32)))) - - ;; perform a future.read which will block, and add this future to the waitable-set - ;; being waited on by 'blocker' - (local.set $ret (call $future.read (local.get $futr) (i32.const 0xdeadbeef))) - (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) - (then unreachable)) - (call $waitable.join (local.get $futr) (global.get $ws)) - - ;; perform a future.write which will rendezvous with the write and complete - (local.set $ret (call $future.write (local.get $futw) (i32.const 0xdeadbeef))) - (if (i32.ne (i32.const 0 (; COMPLETED ;)) (local.get $ret)) - (then unreachable)) - - (i32.const 43) - ) - - (func (export "sync-async-func") - unreachable - ) - (func (export "async-async-func") (result i32) - unreachable - ) - (func (export "async-async-func-cb") (param i32 i32 i32) (result i32) - unreachable - ) - ) - (type $FT (future)) - (canon waitable.join (core func $waitable.join)) - (canon waitable-set.new (core func $waitable-set.new)) - (canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait)) - (canon future.new $FT (core func $future.new)) - (canon future.read $FT async (core func $future.read)) - (canon future.write $FT async (core func $future.write)) - (core instance $cm (instantiate $CM (with "" (instance - (export "mem" (memory $memory "mem")) - (export "waitable.join" (func $waitable.join)) - (export "waitable-set.new" (func $waitable-set.new)) - (export "waitable-set.wait" (func $waitable-set.wait)) - (export "future.new" (func $future.new)) - (export "future.read" (func $future.read)) - (export "future.write" (func $future.write)) - )))) - (func (export "blocker") async (result u32) (canon lift (core func $cm "blocker"))) - (func (export "unblocker") (result u32) (canon lift (core func $cm "unblocker"))) - (func (export "sync-async-func") async (canon lift (core func $cm "sync-async-func"))) - (func (export "async-async-func") async (canon lift (core func $cm "async-async-func") async (callback (func $cm "async-async-func-cb")))) - ) - (component $D - (import "c" (instance $c - (export "blocker" (func async (result u32))) - (export "unblocker" (func (result u32))) - (export "sync-async-func" (func async)) - (export "async-async-func" (func async)) - )) - - (core module $Memory (memory (export "mem") 1)) - (core instance $memory (instantiate $Memory)) - (core module $Core - (import "" "mem" (memory 1)) - (import "" "task.return" (func $task.return (param i32))) - (import "" "subtask.cancel" (func $subtask.cancel (param i32) (result i32))) - (import "" "thread.yield" (func $thread.yield (result i32))) - (import "" "thread.suspend" (func $thread.suspend (result i32))) - (import "" "waitable.join" (func $waitable.join (param i32 i32))) - (import "" "waitable-set.new" (func $waitable-set.new (result i32))) - (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32))) - (import "" "waitable-set.poll" (func $waitable-set.poll (param i32 i32) (result i32))) - (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32))) - (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32))) - (import "" "future.read" (func $future.read (param i32 i32) (result i32))) - (import "" "future.write" (func $future.write (param i32 i32) (result i32))) - (import "" "stream.cancel-read" (func $stream.cancel-read (param i32) (result i32))) - (import "" "stream.cancel-write" (func $stream.cancel-write (param i32) (result i32))) - (import "" "future.cancel-read" (func $future.cancel-read (param i32) (result i32))) - (import "" "future.cancel-write" (func $future.cancel-write (param i32) (result i32))) - (import "" "blocker" (func $blocker (param i32) (result i32))) - (import "" "unblocker" (func $unblocker (result i32))) - (import "" "await-sync-async-func" (func $await-sync-async-func)) - (import "" "await-async-async-func" (func $await-async-async-func)) - - (func (export "sync-barges-in") (result i32) - (local $ret i32) (local $retp1 i32) (local $retp2 i32) - (local $subtask i32) (local $ws i32) (local $event_code i32) - - (local.set $retp1 (i32.const 8)) - (local.set $retp2 (i32.const 12)) - - ;; call $blocker which will block during a synchronous function. - (local.set $ret (call $blocker (local.get $retp1))) - (if (i32.ne (i32.const 1 (; STARTED ;)) (i32.and (local.get $ret) (i32.const 0xf))) - (then unreachable)) - (local.set $subtask (i32.shr_u (local.get $ret) (i32.const 4))) - - ;; normally calling another function would hit backpressure until - ;; $blocker was done, but calling the sync-typed function $unblocker - ;; barges in synchronously. - (local.set $ret (call $unblocker)) - (if (i32.ne (local.get $ret) (i32.const 43)) - (then unreachable)) - - ;; now wait to confirm that $subtask was actually unblocked - (local.set $ws (call $waitable-set.new)) - (call $waitable.join (local.get $subtask) (local.get $ws)) - (local.set $event_code (call $waitable-set.wait (local.get $ws) (local.get $retp2))) - (if (i32.ne (i32.const 1 (; SUBTASK ;)) (local.get $event_code)) - (then unreachable)) - (if (i32.ne (local.get $subtask) (i32.load (local.get $retp2))) - (then unreachable)) - (if (i32.ne (i32.const 2 (; RETURNED=2 ;)) (i32.load offset=4 (local.get $retp2))) - (then unreachable)) - (if (i32.ne (i32.const 42) (i32.load (local.get $retp1))) - (then unreachable)) - - (i32.const 44) - ) - - (func (export "unreachable-cb") (param i32 i32 i32) (result i32) - unreachable - ) - (func (export "return-42-cb") (param i32 i32 i32) (result i32) - (call $task.return (i32.const 42)) - (i32.const 0 (; EXIT ;)) - ) - - (func (export "trap-if-sync-call-async1") - (call $await-sync-async-func) - ) - (func (export "trap-if-sync-call-async2") - (call $await-async-async-func) - ) - (func (export "trap-if-suspend") - (call $thread.suspend) - unreachable - ) - (func (export "trap-if-wait") - (call $waitable-set.wait (call $waitable-set.new) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-wait-cb") (result i32) - (i32.or - (i32.const 2 (; WAIT ;)) - (i32.shl (call $waitable-set.new) (i32.const 4))) - ) - (func (export "trap-if-poll") - (call $waitable-set.poll (call $waitable-set.new) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-poll-cb") (result i32) - (i32.or - (i32.const 3 (; POLL ;)) - (i32.shl (call $waitable-set.new) (i32.const 4))) - ) - (func (export "yield-is-fine") (result i32) - (drop (call $thread.yield)) - (i32.const 42) - ) - (func (export "yield-is-fine-cb") (result i32) - (i32.or - (i32.const 1 (; YIELD ;)) - (i32.shl (i32.const 0xdead) (i32.const 4))) - ) - (func (export "trap-if-sync-cancel") - (call $subtask.cancel (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-sync-stream-read") - (call $stream.read (i32.const 0xdead) (i32.const 0xbeef) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-sync-stream-write") - (call $stream.write (i32.const 0xdead) (i32.const 0xbeef) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-sync-future-read") - (call $future.read (i32.const 0xdead) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-sync-future-write") - (call $future.write (i32.const 0xdead) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-sync-stream-cancel-read") - (call $stream.cancel-read (i32.const 0xdead)) - unreachable - ) - (func (export "trap-if-sync-stream-cancel-write") - (call $stream.cancel-write (i32.const 0xdead)) - unreachable - ) - (func (export "trap-if-sync-future-cancel-read") - (call $future.cancel-read (i32.const 0xdead) (i32.const 0xdeadbeef)) - unreachable - ) - (func (export "trap-if-sync-future-cancel-write") - (call $future.cancel-write (i32.const 0xdead) (i32.const 0xdeadbeef)) - unreachable - ) - ) - (type $FT (future u8)) - (type $ST (stream u8)) - (canon task.return (result u32) (core func $task.return)) - (canon subtask.cancel (core func $subtask.cancel)) - (canon thread.yield (core func $thread.yield)) - (canon thread.suspend (core func $thread.suspend)) - (canon waitable.join (core func $waitable.join)) - (canon waitable-set.new (core func $waitable-set.new)) - (canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait)) - (canon waitable-set.poll (memory $memory "mem") (core func $waitable-set.poll)) - (canon stream.read $ST (memory $memory "mem") (core func $stream.read)) - (canon stream.write $ST (memory $memory "mem") (core func $stream.write)) - (canon future.read $FT (memory $memory "mem") (core func $future.read)) - (canon future.write $FT (memory $memory "mem") (core func $future.write)) - (canon stream.cancel-read $ST (core func $stream.cancel-read)) - (canon stream.cancel-write $ST (core func $stream.cancel-write)) - (canon future.cancel-read $FT (core func $future.cancel-read)) - (canon future.cancel-write $FT (core func $future.cancel-write)) - (canon lower (func $c "blocker") (memory $memory "mem") async (core func $blocker')) - (canon lower (func $c "unblocker") (core func $unblocker')) - (canon lower (func $c "sync-async-func") (core func $await-sync-async-func')) - (canon lower (func $c "async-async-func") (core func $await-async-async-func')) - (core instance $core (instantiate $Core (with "" (instance - (export "mem" (memory $memory "mem")) - (export "task.return" (func $task.return)) - (export "subtask.cancel" (func $subtask.cancel)) - (export "thread.yield" (func $thread.yield)) - (export "thread.suspend" (func $thread.suspend)) - (export "waitable.join" (func $waitable.join)) - (export "waitable-set.new" (func $waitable-set.new)) - (export "waitable-set.wait" (func $waitable-set.wait)) - (export "waitable-set.poll" (func $waitable-set.poll)) - (export "stream.read" (func $stream.read)) - (export "stream.write" (func $stream.write)) - (export "future.read" (func $future.read)) - (export "future.write" (func $future.write)) - (export "stream.cancel-read" (func $stream.cancel-read)) - (export "stream.cancel-write" (func $stream.cancel-write)) - (export "future.cancel-read" (func $future.cancel-read)) - (export "future.cancel-write" (func $future.cancel-write)) - (export "blocker" (func $blocker')) - (export "unblocker" (func $unblocker')) - (export "await-sync-async-func" (func $await-sync-async-func')) - (export "await-async-async-func" (func $await-async-async-func')) - )))) - (func (export "sync-barges-in") async (result u32) (canon lift (core func $core "sync-barges-in"))) - (func (export "trap-if-suspend") (canon lift (core func $core "trap-if-suspend"))) - (func (export "trap-if-wait") (canon lift (core func $core "trap-if-wait"))) - (func (export "trap-if-wait-cb") (canon lift (core func $core "trap-if-wait-cb") async (callback (func $core "unreachable-cb")))) - (func (export "trap-if-poll") (canon lift (core func $core "trap-if-poll"))) - (func (export "trap-if-poll-cb") (canon lift (core func $core "trap-if-poll-cb") async (callback (func $core "unreachable-cb")))) - (func (export "yield-is-fine") (result u32) (canon lift (core func $core "yield-is-fine"))) - (func (export "yield-is-fine-cb") (result u32) (canon lift (core func $core "yield-is-fine-cb") async (callback (func $core "return-42-cb")))) - (func (export "trap-if-sync-call-async1") (canon lift (core func $core "trap-if-sync-call-async1"))) - (func (export "trap-if-sync-call-async2") (canon lift (core func $core "trap-if-sync-call-async2"))) - (func (export "trap-if-sync-cancel") (canon lift (core func $core "trap-if-sync-cancel"))) - (func (export "trap-if-sync-stream-read") (canon lift (core func $core "trap-if-sync-stream-read"))) - (func (export "trap-if-sync-stream-write") (canon lift (core func $core "trap-if-sync-stream-write"))) - (func (export "trap-if-sync-future-read") (canon lift (core func $core "trap-if-sync-future-read"))) - (func (export "trap-if-sync-future-write") (canon lift (core func $core "trap-if-sync-future-write"))) - (func (export "trap-if-sync-stream-cancel-read") (canon lift (core func $core "trap-if-sync-stream-cancel-read"))) - (func (export "trap-if-sync-stream-cancel-write") (canon lift (core func $core "trap-if-sync-stream-cancel-write"))) - (func (export "trap-if-sync-future-cancel-read") (canon lift (core func $core "trap-if-sync-future-cancel-read"))) - (func (export "trap-if-sync-future-cancel-write") (canon lift (core func $core "trap-if-sync-future-cancel-write"))) - ) - (instance $c (instantiate $C)) - (instance $d (instantiate $D (with "c" (instance $c)))) - (func (export "sync-barges-in") (alias export $d "sync-barges-in")) - (func (export "trap-if-sync-call-async1") (alias export $d "trap-if-sync-call-async1")) - (func (export "trap-if-sync-call-async2") (alias export $d "trap-if-sync-call-async2")) - (func (export "trap-if-suspend") (alias export $d "trap-if-suspend")) - (func (export "trap-if-wait") (alias export $d "trap-if-wait")) - (func (export "trap-if-wait-cb") (alias export $d "trap-if-wait-cb")) - (func (export "trap-if-poll") (alias export $d "trap-if-poll")) - (func (export "trap-if-poll-cb") (alias export $d "trap-if-poll-cb")) - (func (export "yield-is-fine") (alias export $d "yield-is-fine")) - (func (export "yield-is-fine-cb") (alias export $d "yield-is-fine-cb")) - (func (export "trap-if-sync-cancel") (alias export $d "trap-if-sync-cancel")) - (func (export "trap-if-sync-stream-read") (alias export $d "trap-if-sync-stream-read")) - (func (export "trap-if-sync-stream-write") (alias export $d "trap-if-sync-stream-write")) - (func (export "trap-if-sync-future-read") (alias export $d "trap-if-sync-future-read")) - (func (export "trap-if-sync-future-write") (alias export $d "trap-if-sync-future-write")) - (func (export "trap-if-sync-stream-cancel-read") (alias export $d "trap-if-sync-stream-cancel-read")) - (func (export "trap-if-sync-stream-cancel-write") (alias export $d "trap-if-sync-stream-cancel-write")) - (func (export "trap-if-sync-future-cancel-read") (alias export $d "trap-if-sync-future-cancel-read")) - (func (export "trap-if-sync-future-cancel-write") (alias export $d "trap-if-sync-future-cancel-write")) -) - -(component instance $i $Tester) -(assert_return (invoke "sync-barges-in") (u32.const 44)) - -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-call-async1") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-call-async2") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-suspend") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-wait") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-wait-cb") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-poll") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-poll-cb") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_return (invoke "yield-is-fine") (u32.const 42)) -(component instance $i $Tester) -(assert_return (invoke "yield-is-fine-cb") (u32.const 42)) -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-cancel") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-stream-read") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-stream-write") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-future-read") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-future-write") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-stream-cancel-read") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-stream-cancel-write") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-future-cancel-read") "cannot block a synchronous task before returning") -(component instance $i $Tester) -(assert_trap (invoke "trap-if-sync-future-cancel-write") "cannot block a synchronous task before returning")