From 15ad76ff41a9c8c417df06c247eadae3d8e827f3 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Sat, 31 Oct 2026 19:18:42 -0700 Subject: [PATCH 01/25] Support async functions, streams, futures and error-contexts in fuzz mode - Codegen reads p3 bindings: skip wit-bindgen's wit_stream/wit_future modules, keep wit_bindgen:: paths, and track async functions and the streams and futures in each signature. - The start function of replay, fuzz and dialog is async, so it can await async exports. - proxy:executor (assets/executor.{wit,rs}) runs the virtualized host's side of streams and futures, which sync mocks cannot wait for. The imports component exports it, and start runs it along with its own. - Fuzz uses a local Fuzz trait instead of Arbitrary, so that streams and futures can implement it. The host writes fuzzed chunks to the streams it returns, and reads the guest's to the end, partway, or not at all. Async imports return at once or after yielding, and start yields between export calls, so the schedule is fuzzed too. - Fuzz and dialog mocks are labeled by the handles wasmtime gives them, set in a _resource_new override, and labeled alike in both generated components through get-handle-mock-* conversion functions. --- Makefile | 12 +- assets/executor.rs | 72 +++ assets/executor.wit | 9 + assets/recorder.wit | 4 +- src/ast.rs | 26 +- src/codegen/dialog.rs | 55 +- src/codegen/fuzz.rs | 103 +++- src/codegen/mock.rs | 67 +++ src/codegen/mod.rs | 98 ++- src/codegen/parse.rs | 17 +- src/codegen/replay.rs | 14 +- src/codegen/util.rs | 30 +- src/codegen/wit.rs | 16 +- src/run.rs | 13 +- src/tests/codegen.rs | 29 +- src/traits/dialog.rs | 36 +- src/traits/fuzz.rs | 328 +++++++--- src/traits/mod.rs | 9 +- src/traits/wave.rs | 9 +- src/wit_util.rs | 31 +- tests/fixtures/proxy-p3-run/guest/Cargo.lock | 400 +++++++++++++ tests/fixtures/proxy-p3-run/guest/Cargo.toml | 13 + tests/fixtures/proxy-p3-run/guest/src/lib.rs | 59 ++ tests/fixtures/proxy-p3/component.wit | 7 + tests/fixtures/proxy-p3/deps/p3.wit | 29 + tests/snapshots/dialog/record_exports/lib.rs | 131 ++-- tests/snapshots/dialog/record_imports/lib.rs | 131 +++- tests/snapshots/dialog/wit/component.wit | 2 + .../snapshots/dialog/wit/deps/conversion.wit | 1 + tests/snapshots/fuzz/record_exports/lib.rs | 301 +++++++--- tests/snapshots/fuzz/record_imports/lib.rs | 314 +++++++--- tests/snapshots/fuzz/wit/component.wit | 2 + tests/snapshots/fuzz/wit/deps/conversion.wit | 1 + tests/snapshots/p3-fuzz/record_exports/lib.rs | 559 ++++++++++++++++++ tests/snapshots/p3-fuzz/record_imports/lib.rs | 544 +++++++++++++++++ tests/snapshots/p3-fuzz/wit/component.wit | 19 + .../snapshots/p3-fuzz/wit/deps/conversion.wit | 6 + tests/snapshots/replay/record_exports/lib.rs | 69 ++- tests/snapshots/replay/record_imports/lib.rs | 67 +++ tests/snapshots/replay/wit/component.wit | 2 + tests/wasi_http_p3.wasm | Bin 0 -> 1145665 bytes 41 files changed, 3188 insertions(+), 447 deletions(-) create mode 100644 assets/executor.rs create mode 100644 assets/executor.wit create mode 100644 src/codegen/mock.rs create mode 100644 tests/fixtures/proxy-p3-run/guest/Cargo.lock create mode 100644 tests/fixtures/proxy-p3-run/guest/Cargo.toml create mode 100644 tests/fixtures/proxy-p3-run/guest/src/lib.rs create mode 100644 tests/fixtures/proxy-p3/component.wit create mode 100644 tests/fixtures/proxy-p3/deps/p3.wit create mode 100644 tests/snapshots/p3-fuzz/record_exports/lib.rs create mode 100644 tests/snapshots/p3-fuzz/record_imports/lib.rs create mode 100644 tests/snapshots/p3-fuzz/wit/component.wit create mode 100644 tests/snapshots/p3-fuzz/wit/deps/conversion.wit create mode 100644 tests/wasi_http_p3.wasm diff --git a/Makefile b/Makefile index 5f10073..3f3254a 100644 --- a/Makefile +++ b/Makefile @@ -26,9 +26,16 @@ test: test-cargo test-fuzz test-record test-fixture test-dialog test-cargo: cargo test +# A WASI p3 main component of tests/fixtures/proxy-p3, which uses error-context +P3_GUEST = tests/fixtures/proxy-p3-run/guest +P3_GUEST_OUT = target/fixture-p3 +P3_GUEST_WASM = $(P3_GUEST_OUT)/wasm32-wasip2/release/proxy_p3_guest.wasm test-fuzz: $(MAKE) run-fuzz WASM=tests/calculator.wasm $(MAKE) run-fuzz WASM=tests/wasi_http.wasm + $(MAKE) run-fuzz WASM=tests/wasi_http_p3.wasm + cargo build --manifest-path $(P3_GUEST)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_GUEST_OUT) + $(MAKE) run-fuzz WASM=$(P3_GUEST_WASM) WASMTIME_FLAGS="$(WASMTIME_FLAGS) -W component-model-error-context=y" # build-only test $(INSTRUMENT) -m fuzz tests/rust.wasm $(INSTRUMENT) -m fuzz tests/go.wasm @@ -66,10 +73,11 @@ test-fixture: $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm target/release/proxy-component run composed.wasm --invoke 'start()' --trace $(FIXTURE_OUT)/trace.out +# Dialog mode has no streams or futures yet, which the p3 component has. test-dialog: rm tests/composed.wasm || true - for wasm in tests/*.wasm; do \ - $(MAKE) run-dialog WASM=$$wasm; \ + for wasm in $(filter-out tests/wasi_http_p3.wasm,$(wildcard tests/*.wasm)); do \ + $(MAKE) run-dialog WASM=$$wasm || exit 1; \ done run-fuzz: diff --git a/assets/executor.rs b/assets/executor.rs new file mode 100644 index 0000000..102a0ee --- /dev/null +++ b/assets/executor.rs @@ -0,0 +1,72 @@ +// The background work of the virtualized host, e.g. writing a stream it returns, or reading one +// the guest gives it. +// +// Streams and futures themselves are not virtualized: `stream.new`, `stream.read` and the like +// are canonical built-ins, which the runtime implements, not imports. What the virtualized host +// plays is the other end. Only readable ends cross components; the writable end stays with the +// component that created the pair. So when a mock returns a stream, e.g. `consume-body`, it +// creates the pair, and must write to it after it has returned. Linking to the real host's +// streams would mean not virtualizing that interface, and in fuzz and replay modes there is no +// host. Playing the other end lets the host choose what it writes, and how it reads, e.g. +// stopping partway; replay the recorded chunks; observe what the guest writes; and put mocked +// resources in the values, e.g. the `fields` of a `future, error-code>>`. +// +// A sync function can neither wait for a stream or future, nor spawn a task, so `spawn` queues +// the work, and `run_executor` polls it from an async task, which lives for the whole run: the +// `proxy:executor/executor.run` export, which the generated `start` calls, or `start` itself. +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR: std::cell::RefCell = std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR.with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + // Wakes `run_executor` from another task, with wit-bindgen's `inter-task-wakeup`. + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + // A job may spawn another, so `EXECUTOR` isn't borrowed while jobs run. + loop { + let mut queued = EXECUTOR.with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR.with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR.with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/assets/executor.wit b/assets/executor.wit new file mode 100644 index 0000000..f15cc3b --- /dev/null +++ b/assets/executor.wit @@ -0,0 +1,9 @@ +package proxy:executor; + +// Runs the background work of a virtualized host, e.g. writing a stream it returns, which a sync +// function cannot wait for. The generated `imports` component exports it, and the `start` +// function of the `exports` component runs it. See `assets/executor.rs`. +interface executor { + run: async func(); + stop: func(); +} diff --git a/assets/recorder.wit b/assets/recorder.wit index ec13214..8c2f6d2 100644 --- a/assets/recorder.wit +++ b/assets/recorder.wit @@ -11,8 +11,10 @@ interface replay { replay-import: func(method: option, args: option>) -> option; } +// Async, so that it can await the main component's async exports, and run the jobs of +// `proxy:executor`. interface start-replay { - start: func(); + start: async func(); } world host { diff --git a/src/ast.rs b/src/ast.rs index d7aca9c..1fa40b4 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -43,6 +43,11 @@ impl<'a> Opt<'a> { need_debug: false, } } + /// Whether the main component's exports are driven by the generated `start`, rather than + /// by the host, which also runs `proxy:executor`. + fn has_start(&self) -> bool { + matches!(self.mode, Mode::Replay | Mode::Fuzz | Mode::Dialog) + } fn push_mode_imports(&self, out: &mut Source) { match self.mode { Mode::Record | Mode::Replay => { @@ -67,6 +72,9 @@ impl<'a> Opt<'a> { out.push_str("world imports {\n"); self.push_mode_imports(&mut out); out.push_str("export proxy:conversion/conversion;\n"); + if self.has_start() { + out.push_str("export proxy:executor/executor;\n"); + } for name in interface_names(resolve, &world.imports)? { // Don't virtualize util imports if name.starts_with("proxy:util/") { @@ -100,7 +108,7 @@ impl<'a> Opt<'a> { } self.main_exports.insert(name); } - if matches!(self.mode, Mode::Replay | Mode::Fuzz | Mode::Dialog) { + if self.has_start() { out.push_str("export proxy:recorder/start-replay@0.1.0;\n") } out.push_str("}\n"); @@ -118,6 +126,9 @@ impl<'a> Opt<'a> { out.push_str("world exports {\n"); self.push_mode_imports(&mut out); out.push_str("import proxy:conversion/conversion;\n"); + if self.has_start() { + out.push_str("import proxy:executor/executor;\n"); + } for name in interface_names(resolve, &world.imports)? { out.push_str(&format!("import {name};\n")); } @@ -214,6 +225,10 @@ impl<'a> Opt<'a> { "deps/util.wit", include_str!("../assets/util.wit").as_bytes(), ); + files.push( + "deps/executor.wit", + include_str!("../assets/executor.wit").as_bytes(), + ); Ok(()) } /// Generate the `proxy:conversion` interface for converting the resources of the interfaces @@ -285,6 +300,13 @@ impl<'a> Opt<'a> { out.push_str(&format!( "get-mock-{func_name}: func(handle: u32) -> {func_name};\n" )); + // The handle of a mock in the component that made it, which labels it in + // both generated components. + if matches!(self.mode, Mode::Fuzz | Mode::Dialog) { + out.push_str(&format!( + "get-handle-mock-{func_name}: func(x: borrow<{func_name}>) -> u32;\n" + )); + } } } } @@ -313,7 +335,7 @@ impl<'a> Opt<'a> { self.need_debug = true; LinkType::Debug } - "proxy:conversion/conversion" => LinkType::Imports, + "proxy:conversion/conversion" | "proxy:executor/executor" => LinkType::Imports, "proxy:util/dialog" => LinkType::Host, name if name.starts_with("proxy:recorder/") => LinkType::Recorder, name if matches!(self.mode, Mode::Record) => match strip_wrapped(name) { diff --git a/src/codegen/dialog.rs b/src/codegen/dialog.rs index 37383fa..184460c 100644 --- a/src/codegen/dialog.rs +++ b/src/codegen/dialog.rs @@ -19,11 +19,18 @@ impl State { if ret_ty.is_some() { let params = params_to_wave(kind.as_ref(), &args); let ret_name = wit_func.result.as_ref().unwrap(); - let read_ret = match wit_func.constructor_resource() { - Some(name) => { - quote! { MockedResource { name: #name.to_string(), ..Dialog::read_value(0) } } - } - None => quote! { Dialog::read_value(0) }, + // A constructor's mock has no handle yet, so `_resource_new` prints its `ret`. + let (read_ret, print_ret) = match wit_func.constructor_resource() { + Some(name) => ( + quote! { MockedResource { name: #name.to_string(), ..Dialog::read_value(0) } }, + quote! { PENDING_RET.with(|pending| pending.set(true)); }, + ), + None => ( + quote! { Dialog::read_value(0) }, + quote! { + proxy::util::dialog::print(0, &format!("ret: {}", wasm_wave::to_string(&ToValue::to_value(&ret)).unwrap())); + }, + ), }; parse_quote! { #sig { @@ -31,7 +38,7 @@ impl State { proxy::util::dialog::print(0, &format!("import: {}({})", #display_name, __params.join(", "))); proxy::util::dialog::print(0, &format!("return type: {}", #ret_name)); let ret = #read_ret; - proxy::util::dialog::print(0, &format!("ret: {}", wasm_wave::to_string(&ToValue::to_value(&ret)).unwrap())); + #print_ret ret } } @@ -49,13 +56,12 @@ impl State { let arms: Vec<_> = funcs .iter() .map(|func| { + let call = func.call(); let ExportFunc { display_name, - func, arg_names, arg_wit_tys, arg_tys, - call_params, .. } = func; quote! { @@ -68,28 +74,29 @@ impl State { __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* proxy::util::dialog::print(0, &format!("export: {}({})", #display_name, __params.join(", "))); - let _ = #func(#(#call_params),*); + let _ = #call; } } }) .collect(); let func_len = arms.len(); let idxs = 1..=func_len; - parse_quote! { - #sig { - loop { - let idx = proxy::util::dialog::read_select(0, "Select an export function to call", &#display_names) as usize; - match idx { - 0 => break, - #(#idxs => #arms)* - _ => unreachable!(), + self.generate_start( + sig, + quote! { + loop { + let idx = proxy::util::dialog::read_select(0, "Select an export function to call", &#display_names) as usize; + match idx { + 0 => break, + #(#idxs => #arms)* + _ => unreachable!(), + } + // clean up borrowed resources from input args + SCOPED_ALLOC.with(|alloc| { + alloc.borrow_mut().clear(); + }); } - // clean up borrowed resources from input args - SCOPED_ALLOC.with(|alloc| { - alloc.borrow_mut().clear(); - }); - } - } - } + }, + ) } } diff --git a/src/codegen/fuzz.rs b/src/codegen/fuzz.rs index 6432e08..0805363 100644 --- a/src/codegen/fuzz.rs +++ b/src/codegen/fuzz.rs @@ -16,31 +16,79 @@ impl State { let display_name = &wit_func.display_name; let args = extract_arg_info(sig); let ret_ty = get_return_type(&sig.output); - if ret_ty.is_some() { - let params = params_to_wave(kind.as_ref(), &args); - let gen_ret = match wit_func.constructor_resource() { - Some(name) => { - quote! { MockedResource { name: #name.to_string(), ..u.arbitrary().unwrap() } } + // The streams and futures the guest gives the host, which the host reads or drops. + let consumed: Vec<_> = args + .iter() + .zip(&wit_func.params_async) + .filter(|(arg, is_async)| **is_async && !arg.is_borrowed) + .map(|(arg, _)| &arg.ident) + .collect(); + let is_async = wit_func.is_async; + if ret_ty.is_none() && consumed.is_empty() && !is_async { + return parse_quote! { + #[allow(unused_variables)] + #sig {} + }; + } + let params = params_to_wave(kind.as_ref(), &args); + // What the host does, rather than returns: how it reads the streams and futures, and when + // an async import returns, at once (RETURNED), or after the guest and the host's + // background work ran a while (STARTED). Random, rather than drawn from the text of the + // arguments, whose first byte is mostly the `#` of a label. + let host = if consumed.is_empty() && !is_async { + quote! {} + } else { + let delay = if is_async { + quote! { + for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { + wit_bindgen::yield_async().await; + } } - None => quote! { u.arbitrary().unwrap() }, + } else { + quote! {} + }; + quote! { + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + #(Fuzz::consume(#consumed, &mut __host);)* + #delay + } + }; + let preamble = quote! { + #params + proxy::util::debug::print(&format!("import: {}({})", #display_name, __params.join(","))); + #host + }; + if ret_ty.is_some() { + // A constructor's mock has no handle yet, so `_resource_new` prints its `ret`. + let (gen_ret, print_ret) = match wit_func.constructor_resource() { + Some(name) => ( + quote! { MockedResource { name: #name.to_string(), ..Fuzz::fuzz(&mut u).unwrap() } }, + quote! { PENDING_RET.with(|pending| pending.set(true)); }, + ), + None => ( + quote! { Fuzz::fuzz(&mut u).unwrap() }, + quote! { + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + proxy::util::debug::print(&format!("ret: {}", res_str)); + }, + ), }; parse_quote! { #sig { - #params - let mut __buf = __params.join(","); - proxy::util::debug::print(&format!("import: {}({})", #display_name, __buf)); - __buf += #display_name; + #preamble + let __buf = __params.join(",") + #display_name; let mut u = Unstructured::new(&__buf.as_bytes()); let res = #gen_ret; - let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + #print_ret res } } } else { parse_quote! { - #[allow(unused_variables)] - #sig {} + #sig { + #preamble + } } } } @@ -49,31 +97,42 @@ impl State { .export_funcs() .into_iter() .map(|func| { + let call = func.call(); let ExportFunc { display_name, - func, arg_names, arg_tys, - call_params, + ret_async, .. } = func; + // The streams and futures the guest returns, which the host reads or drops. + let ret = if ret_async { + quote! { Fuzz::consume(#call, &mut u); } + } else { + quote! { let _ = #call; } + }; quote! { { let mut __params: Vec = Vec::new(); #( - let #arg_names: #arg_tys = u.arbitrary().unwrap(); + let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* proxy::util::debug::print(&format!("export: {}({})", #display_name, __params.join(", "))); - let _ = #func(#(#call_params),*); + #ret + // Lets the guest, and the host's background work, run between calls. + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } } }) .collect(); let func_len = arms.len(); let idxs = 1..=func_len; - parse_quote! { - #sig { + self.generate_start( + sig, + quote! { let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -87,7 +146,7 @@ impl State { alloc.borrow_mut().clear(); }); } - } - } + }, + ) } } diff --git a/src/codegen/mock.rs b/src/codegen/mock.rs new file mode 100644 index 0000000..eb8d4ed --- /dev/null +++ b/src/codegen/mock.rs @@ -0,0 +1,67 @@ +//! The resources of the virtualized host in fuzz and dialog modes, whose mocks the imports +//! component makes: they are labeled by the handles wasmtime gives them. +use super::State; +use crate::Mode; +use quote::quote; +use syn::{Item, parse_quote}; + +impl State { + /// Overrides wit-bindgen's `_resource_new`, which makes the handle of every new mock of the + /// resource `name` of `module_path`, including those constructors return, to label the mock + /// by its handle. As a constructor returns the mock before it has a handle, the constructor + /// sets `PENDING_RET`, and the `ret` of the call is printed here. + pub fn generate_mock_resource_new( + &self, + module_path: &[String], + name: &str, + ) -> syn::ImplItemFn { + let iface = self.interfaces[module_path]; + let module = format!("[export]{}", self.resolve.id_of(iface).unwrap()); + let id = self.wit_type_id(module_path, name).unwrap(); + let link_name = format!( + "[resource-new]{}", + self.resolve.types[id].name.as_ref().unwrap() + ); + let print = match self.mode { + Mode::Fuzz => quote! { proxy::util::debug::print(&ret) }, + Mode::Dialog => quote! { proxy::util::dialog::print(0, &ret) }, + _ => unreachable!(), + }; + parse_quote! { + unsafe fn _resource_new(val: *mut u8) -> u32 { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = #module)] + unsafe extern "C" { + #[link_name = #link_name] + fn new(_: *mut u8) -> i32; + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn new(_: *mut u8) -> i32 { + unreachable!() + } + let handle = unsafe { new(val) } as u32; + let mock = unsafe { &mut *(val as *mut Option) } + .as_mut() + .unwrap(); + mock.handle = handle; + if PENDING_RET.with(|pending| pending.take()) { + let ret = format!("ret: {}", wasm_wave::to_string(&mock.to_value()).unwrap()); + #print; + } + handle + } + } + } + /// Items the mocks use. + pub fn mock_defs(&self) -> Vec { + let file: syn::File = parse_quote! { + thread_local! { + // Set by a mocked constructor, whose `ret` `_resource_new` prints, once the mock + // it returned has a handle. + #[allow(dead_code)] + static PENDING_RET: std::cell::Cell = const { std::cell::Cell::new(false) }; + } + }; + file.items + } +} diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 8493f3d..3643018 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -1,5 +1,6 @@ use crate::Mode; use anyhow::Result; +use proc_macro2::TokenStream; use quote::quote; use std::collections::{BTreeMap, BTreeSet}; use syn::{ @@ -14,6 +15,7 @@ use wit_parser::{InterfaceId, Resolve, TypeId, WorldId}; mod dialog; mod fuzz; +mod mock; mod parse; mod record; mod replay; @@ -34,6 +36,10 @@ pub struct State { pub types: BTreeMap, Vec<(TypeId, TypeInfo)>>, pub funcs: BTreeMap, BTreeMap, Vec>>, pub module_paths: BTreeSet>, + /// Whether the bindings have streams, i.e. the `wit_stream` module + pub has_streams: bool, + /// Whether the bindings have futures, i.e. the `wit_future` module + pub has_futures: bool, pub output: Vec, } pub enum TypeInfo { @@ -62,12 +68,17 @@ struct ExportFunc { has_ret: bool, /// Whether the result owns a resource ret_owns: bool, + is_async: bool, + /// Whether the result has a stream or future + ret_async: bool, } /// Interface for converting resources between the generated components. const CONVERSION_MODULE: &str = "exports::proxy::conversion::conversion"; /// Interface whose `start` function drives the main component's exports in virtualized modes. const START_REPLAY_MODULE: &str = "exports::proxy::recorder::start_replay"; +/// Interface running the background work of the virtualized host, see `assets/executor.rs`. +const EXECUTOR_MODULE: &str = "exports::proxy::executor::executor"; /// Generates the `lib.rs` implementing the traits of `bindings`, which wit-bindgen generated /// from `world` of `resolve`. @@ -89,7 +100,16 @@ pub fn generate( let defs = state.replay_defs(); state.output.extend(defs); } + if matches!(mode, Mode::Fuzz | Mode::Dialog) { + let defs = state.mock_defs(); + state.output.extend(defs); + } + let has_executor = state.has_executor(); let mut file = state.into_output_file(); + if has_executor { + let executor = syn::parse_file(include_str!("../../assets/executor.rs"))?; + file.items.extend(executor.items); + } if use_custom_allocator { // dlmalloc with a memory source that grows by bytes, for `--page-size=1`. let alloc = syn::parse_file(include_str!("../../assets/one_byte_page_alloc.rs"))?; @@ -101,12 +121,27 @@ pub fn generate( } Ok(prettyplease::unparse(&file)) } +impl ExportFunc { + /// The call of the export, awaited if it's async. + fn call(&self) -> TokenStream { + let ExportFunc { + func, call_params, .. + } = self; + if self.is_async { + quote! { #func(#(#call_params),*).await } + } else { + quote! { #func(#(#call_params),*) } + } + } +} impl State { fn new(mode: Mode, resolve: Resolve, world: WorldId) -> Self { State { mode, interfaces: wit::interface_modules(&resolve, world), conversions: wit::conversion_funcs(&resolve), + has_streams: false, + has_futures: false, type_info: { let mut types = wit_bindgen_core::Types::default(); types.analyze(&resolve); @@ -184,6 +219,10 @@ impl State { methods.push(syn::ImplItem::Fn(stub_impl)); continue; } + if module_full_name == EXECUTOR_MODULE { + methods.push(syn::ImplItem::Fn(generate_executor_func(&sig))); + continue; + } let is_start = module_full_name == START_REPLAY_MODULE; if is_start { assert!(sig.ident == "start"); @@ -215,6 +254,9 @@ impl State { methods.push(syn::ImplItem::Fn(self.generate_record_drop(module_path))) } (Mode::Replay, Some(_)) => methods.push(syn::ImplItem::Fn(self.generate_replay_drop())), + (Mode::Fuzz | Mode::Dialog, Some(resource)) => methods.push(syn::ImplItem::Fn( + self.generate_mock_resource_new(module_path, resource), + )), _ => (), } parse_quote! { @@ -228,13 +270,23 @@ impl State { let func_name = &sig.ident.to_string(); let body = if func_name.starts_with("get_wrapped_") || func_name.starts_with("get_host_") { quote! { x.to_proxy() } + } else if func_name.starts_with("get_handle_mock_") { + quote! { x.get::().handle } } else if func_name.starts_with("get_handle_") { quote! { x.to_proxy().handle() } } else if func_name.starts_with("get_mock_") { let resource = get_return_type(&sig.output).unwrap(); let name = self.conversion_resource(&sig.ident); - quote! { - #resource::new(MockedResource { handle, name: #name.to_string() }) + // In fuzz and dialog modes, `_resource_new` labels the mock by its handle (see + // `generate_mock_resource_new`); in replay mode, by the handle in the trace. + match self.mode { + Mode::Fuzz | Mode::Dialog => quote! { + let _ = handle; + #resource::new(MockedResource { handle: 0, name: #name.to_string() }) + }, + _ => quote! { + #resource::new(MockedResource { handle, name: #name.to_string() }) + }, } } else { unreachable!() @@ -285,12 +337,39 @@ impl State { call_params: args.iter().map(|arg| arg.call_param()).collect(), has_ret: get_return_type(&sig.output).is_some(), ret_owns: wit_func.result_owns, + is_async: wit_func.is_async, + ret_async: wit_func.result_async, }); } } } res } + /// Whether the component runs `proxy:executor`: the imports component exports it, and the + /// exports component imports it. + fn has_executor(&self) -> bool { + let import = EXECUTOR_MODULE.strip_prefix("exports::").unwrap(); + self.module_paths + .iter() + .any(|path| [EXECUTOR_MODULE, import].contains(&path.join("::").as_str())) + } + /// The async `start` function running `body`, along with the background work of both + /// generated components (see `assets/executor.rs`), until the exports it called are done. + fn generate_start(&self, sig: &Signature, body: TokenStream) -> syn::ImplItemFn { + parse_quote! { + #sig { + let __executors = ( + wit_bindgen::spawn_local(proxy::executor::executor::run()), + wit_bindgen::spawn_local(run_executor()), + ); + #body + proxy::executor::executor::stop(); + stop_executor(); + __executors.0.await; + __executors.1.await; + } + } + } fn into_output_file(self) -> File { File { items: self.output, @@ -300,6 +379,21 @@ impl State { } } +/// The functions of `proxy:executor/executor`, which the imports component exports, for the +/// `start` function of the exports component to run its jobs (see `assets/executor.rs`). +fn generate_executor_func(sig: &Signature) -> syn::ImplItemFn { + let body = match sig.ident.to_string().as_str() { + "run" => quote! { run_executor().await }, + "stop" => quote! { stop_executor() }, + name => unreachable!("unexpected executor function {name}"), + }; + parse_quote! { + #sig { + #body + } + } +} + pub fn get_proxy_path(src_path: &[String]) -> Vec { assert!(src_path.len() >= 3); let mut res = src_path.to_vec(); diff --git a/src/codegen/parse.rs b/src/codegen/parse.rs index 99a19b1..b310d9f 100644 --- a/src/codegen/parse.rs +++ b/src/codegen/parse.rs @@ -110,8 +110,21 @@ impl State { if let Some((_, ref mod_items)) = module.content { let mut new_path = current_path.clone(); let mod_name = module.ident.to_string(); - if current_path.is_empty() && mod_name == "_rt" { - continue; + // wit-bindgen's runtime, and its `wit_stream`/`wit_future` modules, whose + // payload traits aren't WIT items. + if current_path.is_empty() { + match mod_name.as_str() { + "_rt" => continue, + "wit_stream" => { + self.has_streams = true; + continue; + } + "wit_future" => { + self.has_futures = true; + continue; + } + _ => (), + } } new_path.push(mod_name); self.module_paths.insert(new_path.clone()); diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 0f804cc..76d693d 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -141,12 +141,11 @@ impl State { } pub fn generate_replay_start_func(&self, sig: &Signature) -> syn::ImplItemFn { let arms = self.export_funcs().into_iter().map(|func| { + let call = func.call(); let ExportFunc { display_name, - func, arg_names, arg_tys, - call_params, has_ret, ret_owns, .. @@ -175,15 +174,16 @@ impl State { let arg_value: Value = wasm_wave::from_str(&<#arg_tys as ValueTyped>::value_type(), &#arg_idx).unwrap(); let #arg_names: #arg_tys = arg_value.to_rust(); )* - let res = #func(#(#call_params),*); + let res = #call; #assert_ret #keep_ret } } }); let drop_arm = self.replay_export_drop(); - parse_quote! { - #sig { + self.generate_start( + sig, + quote! { while let Some((method, args)) = proxy::recorder::replay::replay_export() { match method.as_str() { #(#arms)* @@ -195,7 +195,7 @@ impl State { alloc.borrow_mut().clear(); }); } - } - } + }, + ) } } diff --git a/src/codegen/util.rs b/src/codegen/util.rs index 0eca360..701d1a2 100644 --- a/src/codegen/util.rs +++ b/src/codegen/util.rs @@ -60,9 +60,33 @@ pub fn extract_arg_info(sig: &Signature) -> Vec { } const BUILTIN_TYPES: &[&str] = &[ - "Self", "Result", "Option", "Vec", "Box", "Rc", "Arc", "String", "str", "u8", "u16", "u32", - "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", "f32", "f64", "bool", - "char", "_rt", + "Self", + "Result", + "Option", + "Vec", + "Box", + "Rc", + "Arc", + "String", + "str", + "u8", + "u16", + "u32", + "u64", + "u128", + "usize", + "i8", + "i16", + "i32", + "i64", + "i128", + "isize", + "f32", + "f64", + "bool", + "char", + "_rt", + "wit_bindgen", ]; impl<'a> VisitMut for FullTypePath<'a> { fn visit_type_path_mut(&mut self, ty: &mut syn::TypePath) { diff --git a/src/codegen/wit.rs b/src/codegen/wit.rs index 33c7371..be2c1c2 100644 --- a/src/codegen/wit.rs +++ b/src/codegen/wit.rs @@ -4,7 +4,7 @@ //! applying wit-bindgen's naming to the WIT items, never by recovering WIT names from Rust ones. use super::State; use super::util::ResourceFuncKind; -use crate::wit_util::strip_wrapped; +use crate::wit_util::{has_async_value, strip_wrapped}; use heck::ToUpperCamelCase; use std::collections::BTreeMap; use syn::Ident; @@ -32,6 +32,12 @@ pub struct WitFunc { pub result: Option, /// Whether the result owns a resource pub result_owns: bool, + /// Whether the function is `async` + pub is_async: bool, + /// Whether each parameter, without a method's `self`, has a stream or future + pub params_async: Vec, + /// Whether the result has a stream or future + pub result_async: bool, } impl WitFunc { /// In virtualized components, constructors return `Self`, i.e. `MockedResource`, so @@ -281,6 +287,14 @@ impl State { .collect(), result: wit_func.result.as_ref().map(|ty| self.type_name(ty)), result_owns: wit_func.result.is_some_and(|ty| self.owns_resource(&ty)), + is_async: wit_func.kind.is_async(), + params_async: params + .iter() + .map(|param| has_async_value(&self.resolve, ¶m.ty)) + .collect(), + result_async: wit_func + .result + .is_some_and(|ty| has_async_value(&self.resolve, &ty)), }) } /// Like `wit_func`, for functions the bindings generate from WIT. diff --git a/src/run.rs b/src/run.rs index 46e8b81..d5c4a27 100644 --- a/src/run.rs +++ b/src/run.rs @@ -6,7 +6,8 @@ use wasmtime::component::types::{ComponentExtern, ComponentFunc, ComponentItem a use wasmtime::component::wasm_wave::{untyped::UntypedFuncCall, wasm::WasmFunc}; use wasmtime::component::{Component, HasSelf, Linker, ResourceTable, Val}; use wasmtime::*; -use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView, p2::add_to_linker_sync}; +use wasmtime_wasi::runtime::in_tokio; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView, p2::add_to_linker_async}; #[derive(Parser)] pub struct RunArgs { @@ -76,12 +77,16 @@ pub fn run(args: RunArgs) -> anyhow::Result<()> { config .consume_fuel(true) .wasm_custom_page_sizes(true) + // The generated `start` is async, and WASI p3 components have async exports and imports. + .wasm_component_model_async(true) + .concurrency_support(true) //.debug_info(true) .wasm_backtrace_details(WasmBacktraceDetails::Enable); let engine = Engine::new(&config)?; let mut linker = Linker::::new(&engine); - add_to_linker_sync(&mut linker)?; + // The WASI p2 host functions are async ones, as the store runs async exports. + add_to_linker_async(&mut linker)?; let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build(); let mut state = State { wasi_ctx: wasi, @@ -123,14 +128,14 @@ pub fn run(args: RunArgs) -> anyhow::Result<()> { component.get_export_index(instance.as_ref(), name) }) .unwrap(); - let instance = linker.instantiate(&mut store, &component)?; + let instance = in_tokio(linker.instantiate_async(&mut store, &component))?; let param_types = WasmFunc::params(&func_type).collect::>(); let params = untyped_call.to_wasm_params(¶m_types)?; let func = instance.get_func(&mut store, export).unwrap(); let mut results = vec![Val::Bool(false); func_type.results().len()]; // A replayed `wasi:cli/exit` traps the guest on purpose. - func.call(&mut store, ¶ms, &mut results).or_else(|e| { + in_tokio(func.call_async(&mut store, ¶ms, &mut results)).or_else(|e| { if store.data().exit_called { Ok(()) } else { diff --git a/src/tests/codegen.rs b/src/tests/codegen.rs index dd199e3..20f5564 100644 --- a/src/tests/codegen.rs +++ b/src/tests/codegen.rs @@ -7,6 +7,8 @@ use std::fs; use std::path::{Path, PathBuf}; const FIXTURE: &str = "tests/fixtures/proxy"; +/// A WASI p3 main component, with async functions, streams, futures and error-contexts. +const P3_FIXTURE: &str = "tests/fixtures/proxy-p3"; const SNAPSHOTS: &str = "tests/snapshots"; /// The generated files to compare, relative to the generation directory. const OUTPUTS: &[&str] = &[ @@ -57,26 +59,27 @@ fn check(path: &Path, actual: &str) -> Result<(), String> { } /// Generates the sources for `mode` into a new directory. -fn generate(mode: &Mode) -> tempfile::TempDir { +fn generate(fixture: &str, mode: &Mode) -> anyhow::Result { let dir = tempfile::tempdir().unwrap(); - copy_dir(Path::new(FIXTURE), &dir.path().join("wit")); + copy_dir(Path::new(fixture), &dir.path().join("wit")); let args = InstrumentArgs { wasm_file: PathBuf::new(), mode: mode.clone(), use_host_recorder: false, one_byte_page_size: false, }; - generate_sources(&args, dir.path()).unwrap(); - dir + generate_sources(&args, dir.path())?; + Ok(dir) } -fn snapshot(mode: Mode) { - let dir = generate(&mode); +/// Checks the sources generated from `fixture` against `tests/snapshots/{name}`. +fn snapshot(fixture: &str, name: &str, mode: Mode) { + let dir = generate(fixture, &mode).unwrap(); let errors: Vec<_> = OUTPUTS .iter() .filter_map(|output| { let actual = fs::read_to_string(dir.path().join(output)).unwrap(); - let snapshot = Path::new(SNAPSHOTS).join(mode.to_str()).join(output); + let snapshot = Path::new(SNAPSHOTS).join(name).join(output); check(&snapshot, &actual).err() }) .collect(); @@ -89,17 +92,21 @@ fn snapshot(mode: Mode) { #[test] fn record() { - snapshot(Mode::Record); + snapshot(FIXTURE, "record", Mode::Record); } #[test] fn replay() { - snapshot(Mode::Replay); + snapshot(FIXTURE, "replay", Mode::Replay); } #[test] fn fuzz() { - snapshot(Mode::Fuzz); + snapshot(FIXTURE, "fuzz", Mode::Fuzz); } #[test] fn dialog() { - snapshot(Mode::Dialog); + snapshot(FIXTURE, "dialog", Mode::Dialog); +} +#[test] +fn p3_fuzz() { + snapshot(P3_FIXTURE, "p3-fuzz", Mode::Fuzz); } diff --git a/src/traits/dialog.rs b/src/traits/dialog.rs index ff30df0..34c51e3 100644 --- a/src/traits/dialog.rs +++ b/src/traits/dialog.rs @@ -18,16 +18,11 @@ impl Trait for DialogTrait { let wit_name = &wit.name; let in_import = module_path[0] != "exports"; if in_import { - let mock = new_mock(wit, quote! { handle }); + // The imports component, which makes the mock, labels it by its handle. + let mock = new_mock(wit, quote! { 0 }); res.push(parse_quote! { impl Dialog for #resource_path { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID.with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); #mock } } @@ -35,12 +30,6 @@ impl Trait for DialogTrait { res.push(parse_quote! { impl<'a> Dialog for &'a #resource_path { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID.with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); SCOPED_ALLOC.with(|alloc| { let mut alloc = alloc.borrow_mut(); alloc.alloc(#mock) @@ -53,14 +42,9 @@ impl Trait for DialogTrait { res.push(parse_quote! { impl Dialog for #resource_path { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID.with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); + // `_resource_new` gives it its handle. #resource_path::new(MockedResource { - handle, + handle: 0, name: #wit_name.to_string(), }) } @@ -187,9 +171,6 @@ impl Trait for DialogTrait { trait Dialog { fn read_value(dep: u32) -> Self; } - thread_local! { - static HANDLE_ID: std::cell::RefCell = std::cell::RefCell::new(1); - } impl Dialog for () { fn read_value(_dep: u32) -> Self { () @@ -232,14 +213,9 @@ impl Trait for DialogTrait { } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID.with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); + // A constructor's, which `_resource_new` gives its handle. Self { - handle, + handle: 0, name: "mocked-resource".to_string(), } } diff --git a/src/traits/fuzz.rs b/src/traits/fuzz.rs index 9efa6cb..40b6982 100644 --- a/src/traits/fuzz.rs +++ b/src/traits/fuzz.rs @@ -4,7 +4,12 @@ use crate::traits::{Trait, new_mock}; use quote::quote; use syn::{Item, ItemEnum, ItemStruct, parse_quote}; -pub struct FuzzTrait; +pub struct FuzzTrait { + /// Whether the bindings have streams, whose `Fuzz` impl needs their `wit_stream` module + pub has_streams: bool, + /// Whether the bindings have futures, whose `Fuzz` impl needs their `wit_future` module + pub has_futures: bool, +} impl Trait for FuzzTrait { fn resource_trait( @@ -18,20 +23,18 @@ impl Trait for FuzzTrait { let wit_name = &wit.name; let in_import = module_path[0] != "exports"; if in_import { - let mock = new_mock(wit, quote! { 42 }); + // The imports component, which makes the mock, labels it by its handle. + let mock = new_mock(wit, quote! { 0 }); res.push(parse_quote! { - impl Arbitrary<'_> for #resource_path { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { + impl Fuzz for #resource_path { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(#mock) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) - } } }); res.push(parse_quote! { - impl<'a> Arbitrary<'_> for &'a #resource_path { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { + impl<'a> Fuzz for &'a #resource_path { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { SCOPED_ALLOC.with(|alloc| { let mut alloc = alloc.borrow_mut(); Ok(alloc.alloc(#mock)) @@ -42,21 +45,19 @@ impl Trait for FuzzTrait { } else { let borrow_path = make_path(module_path, &format!("{}Borrow<'a>", resource.ident)); res.push(parse_quote! { - impl Arbitrary<'_> for #resource_path { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { + impl Fuzz for #resource_path { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + // `_resource_new` gives it its handle. Ok(#resource_path::new(MockedResource { - handle: 42, + handle: 0, name: #wit_name.to_string(), })) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) - } } }); res.push(parse_quote! { - impl<'a> Arbitrary<'_> for #borrow_path { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { + impl<'a> Fuzz for #borrow_path { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { unreachable!() } } @@ -70,40 +71,26 @@ impl Trait for FuzzTrait { struct_item: &ItemStruct, _wit: &WitType, ) -> Vec { - let mut res = Vec::new(); let struct_name = make_path(module_path, &struct_item.ident.to_string()); let (impl_generics, ty_generics, where_clause) = struct_item.generics.split_for_impl(); - let (field_names, tys) = match &struct_item.fields { - syn::Fields::Unit => (Vec::new(), Vec::new()), - syn::Fields::Named(fields) => { - let field_names: Vec<_> = fields - .named - .iter() - .map(|f| f.ident.clone().unwrap()) - .collect(); - let field_tys = fields.named.iter().map(|f| &f.ty).collect(); - (field_names, field_tys) - } + let field_names: Vec<_> = match &struct_item.fields { + syn::Fields::Unit => Vec::new(), + syn::Fields::Named(fields) => fields + .named + .iter() + .map(|f| f.ident.clone().unwrap()) + .collect(), syn::Fields::Unnamed(_) => unreachable!(), }; - res.push(parse_quote! { - impl #impl_generics Arbitrary<'_> for #struct_name #ty_generics #where_clause { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { + vec![parse_quote! { + impl #impl_generics Fuzz for #struct_name #ty_generics #where_clause { + fn fuzz(u: &mut Unstructured<'_>) -> Result { Ok(#struct_name { - #(#field_names: u.arbitrary()?),* + #(#field_names: Fuzz::fuzz(u)?),* }) } - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (0, Some(0)); - #( - let size = <#tys as Arbitrary>::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - )* - res - } } - }); - res + }] } fn enum_trait( &self, @@ -111,7 +98,6 @@ impl Trait for FuzzTrait { enum_item: &ItemEnum, _wit: &WitType, ) -> Vec { - let mut res = Vec::new(); let enum_name = make_path(module_path, &enum_item.ident.to_string()); let (impl_generics, ty_generics, where_clause) = enum_item.generics.split_for_impl(); let arms = enum_item.variants.iter().enumerate().map(|(idx, variant)| { @@ -121,47 +107,23 @@ impl Trait for FuzzTrait { #idx => Ok(#enum_name::#tag) }, syn::Fields::Unnamed(_) => quote! { - #idx => Ok(#enum_name::#tag(u.arbitrary()?)) + #idx => Ok(#enum_name::#tag(Fuzz::fuzz(u)?)) }, syn::Fields::Named(_) => unreachable!(), } }); - let size_hint = enum_item - .variants - .iter() - .map(|variant| match &variant.fields { - syn::Fields::Unit => quote! {}, - syn::Fields::Unnamed(f) => { - assert!(f.unnamed.len() == 1); - let ty = &f.unnamed.first().unwrap().ty; - quote! { - let size = <#ty as Arbitrary>::size_hint(depth + 1); - res = arbitrary::size_hint::or(res, size); - } - } - syn::Fields::Named(_) => unreachable!(), - }); let variant_len = enum_item.variants.len(); - res.push(parse_quote! { - impl #impl_generics Arbitrary<'_> for #enum_name #ty_generics #where_clause { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { + vec![parse_quote! { + impl #impl_generics Fuzz for #enum_name #ty_generics #where_clause { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(#variant_len)?; match idx { #(#arms),*, _ => unreachable!(), } } - #[allow(unused_variables, unused_mut)] - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (1, Some(1)); - #( - #size_hint - )* - res - } } - }); - res + }] } fn flag_trait( &self, @@ -169,15 +131,14 @@ impl Trait for FuzzTrait { item: &crate::codegen::ItemFlag, _wit: &WitType, ) -> Vec { - let mut res = Vec::new(); let flag_path = make_path(module_path, &item.name.to_string()); let flag_num = item.flags.len() - 1; let flags = item.flags.iter().map(|f| { quote! { #flag_path::#f } }); - res.push(parse_quote! { - impl Arbitrary<'_> for #flag_path { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { + vec![parse_quote! { + impl Fuzz for #flag_path { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let flag_count = u.int_in_range(0..=#flag_num)?; let mut res = #flag_path::empty(); let choices = [#(#flags),*]; @@ -187,29 +148,220 @@ impl Trait for FuzzTrait { } Ok(res) } - fn size_hint(_: usize) -> (usize, Option) { - (1, Some(1 + #flag_num)) - } } - }); - res + }] } fn trait_defs(&self) -> Vec { let ast: syn::File = parse_quote! { #[allow(unused_imports)] use arbitrary::{Arbitrary, Unstructured, Result}; - impl Arbitrary<'_> for MockedResource { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { + // A local trait rather than `Arbitrary`, so that it can be implemented for wit-bindgen's + // streams and futures. + trait Fuzz: Sized { + // A value the virtualized host gives the guest. + fn fuzz(u: &mut Unstructured<'_>) -> Result; + // What the virtualized host does with a value the guest gives it: by default, drop it. + // Only streams and futures override it, which p2 worlds don't have. + #[allow(dead_code)] + fn consume(self, _u: &mut Unstructured<'_>) {} + } + impl Fuzz for MockedResource { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + // A constructor's, which `_resource_new` gives its handle. Ok(Self { - handle: 42, + handle: 0, name: "mocked-resource".to_string(), }) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) + } + macro_rules! impl_fuzz_primitive { + ($($t:ty),*) => { + $( + impl Fuzz for $t { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + u.arbitrary() + } + } + )* + }; + } + impl_fuzz_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool, char, String, ()); + impl Fuzz for Option { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Some(Fuzz::fuzz(u)?) } else { None }) + } + fn consume(self, u: &mut Unstructured<'_>) { + if let Some(x) = self { + x.consume(u); + } + } + } + impl Fuzz for Vec { + // Like `Arbitrary` for `Vec`, which decides to continue before each element. + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let mut res = Vec::new(); + while u.arbitrary()? { + res.push(Fuzz::fuzz(u)?); + } + Ok(res) } + fn consume(self, u: &mut Unstructured<'_>) { + for x in self { + x.consume(u); + } + } + } + impl Fuzz for core::result::Result { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Ok(Fuzz::fuzz(u)?) } else { Err(Fuzz::fuzz(u)?) }) + } + fn consume(self, u: &mut Unstructured<'_>) { + match self { + Ok(x) => x.consume(u), + Err(x) => x.consume(u), + } + } + } + macro_rules! impl_fuzz_tuple { + ($($T:ident),+) => { + impl<$($T: Fuzz),+> Fuzz for ($($T,)+) { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(($($T::fuzz(u)?,)+)) + } + #[allow(non_snake_case)] + fn consume(self, u: &mut Unstructured<'_>) { + let ($($T,)+) = self; + $($T.consume(u);)+ + } + } + }; } + impl_fuzz_tuple!(T1); + impl_fuzz_tuple!(T1, T2); + impl_fuzz_tuple!(T1, T2, T3); + impl_fuzz_tuple!(T1, T2, T3, T4); + impl_fuzz_tuple!(T1, T2, T3, T4, T5); + impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); + impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); + impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); + impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); }; - ast.items + let mut items = ast.items; + items.extend(self.async_defs()); + items + } +} + +impl FuzzTrait { + /// `Fuzz` for streams, futures and error-contexts. The bindings only have the `wit_stream` + /// and `wit_future` modules for them if the world has streams or futures. Their labels, e.g. + /// `#stream-1`, are their handles, as for resources. + /// + /// The virtualized host writes the streams and futures it gives the guest, and reads those + /// the guest gives it, in jobs of `assets/executor.rs`. What it writes, and how it reads, + /// e.g. whether it reads to the end, is fuzzed when the value changes hands. + fn async_defs(&self) -> Vec { + let mut items = Vec::new(); + if self.has_streams { + let ast: syn::File = parse_quote! { + impl Fuzz for wit_bindgen::StreamReader + where + T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, + { + // Writes fuzzed chunks, then ends the stream. + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let (mut writer, reader) = wit_stream::new::(); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let chunks: Vec> = Fuzz::fuzz(u)?; + spawn(async move { + for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { + let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); + if !writer.write_all(chunk).await.is_empty() { + proxy::util::debug::print(&format!("{label} dropped by guest")); + return; + } + proxy::util::debug::print(&format!("{label}.read() = {items}")); + } + proxy::util::debug::print(&format!("{label}.read() = dropped")); + }); + Ok(reader) + } + // Mostly reads to the end; otherwise reads a few chunks, or none, and drops the + // stream. Out of fuzz data, it reads to the end. + fn consume(mut self, u: &mut Unstructured<'_>) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let reads: Option = match u.int_in_range(0..=9u8).unwrap_or(0) { + 8 => Some(u.int_in_range(1..=4).unwrap_or(1)), + 9 => Some(0), + _ => None, + }; + let capacity = u.int_in_range(1..=4096usize).unwrap_or(4096); + spawn(async move { + let mut count = 0; + while reads != Some(count) { + let (result, items) = self.read(Vec::with_capacity(capacity)).await; + if let wit_bindgen::StreamResult::Dropped = result { + proxy::util::debug::print(&format!("{label} -> dropped")); + return; + } + let items = wasm_wave::to_string(&items.to_value()).unwrap(); + proxy::util::debug::print(&format!("{label} -> {items}")); + count += 1; + } + proxy::util::debug::print(&format!("{label} dropped by host")); + }); + } + } + }; + items.extend(ast.items); + } + if self.has_futures { + let ast: syn::File = parse_quote! { + impl Fuzz for wit_bindgen::FutureReader + where + T: Fuzz + ToValue + ValueTyped + wit_future::FuturePayload, + { + // Writes a fuzzed value. + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let value = T::fuzz(u)?; + let (writer, reader) = + wit_future::new::(|| unreachable!("the value is always written")); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + spawn(async move { + if writer.write(value).await.is_ok() { + proxy::util::debug::print(&format!("{label}.read() = {wave}")); + } else { + proxy::util::debug::print(&format!("{label} dropped by guest")); + } + }); + Ok(reader) + } + // Mostly reads the value; otherwise drops the future. + fn consume(self, u: &mut Unstructured<'_>) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let read = u.int_in_range(0..=9u8).unwrap_or(0) != 9; + spawn(async move { + if read { + let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); + proxy::util::debug::print(&format!("{label} -> {value}")); + } else { + drop(self); + proxy::util::debug::print(&format!("{label} dropped by host")); + } + }); + } + } + }; + items.extend(ast.items); + } + items.push(parse_quote! { + impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(Self::new(&String::fuzz(u)?)) + } + } + }); + items } } diff --git a/src/traits/mod.rs b/src/traits/mod.rs index d079e2d..9a110a9 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -60,6 +60,7 @@ impl<'a> TraitGenerator<'a> { to_rust: false, has_replay_table: false, wrapped_labels: true, + mock_labels: false, })); traits.push(Box::new(proxy::ProxyTrait::new(state))); } @@ -69,6 +70,7 @@ impl<'a> TraitGenerator<'a> { to_rust: true, has_replay_table: true, wrapped_labels: false, + mock_labels: false, })); } Mode::Fuzz => { @@ -77,8 +79,12 @@ impl<'a> TraitGenerator<'a> { to_rust: false, has_replay_table: true, wrapped_labels: false, + mock_labels: true, + })); + traits.push(Box::new(fuzz::FuzzTrait { + has_streams: state.has_streams, + has_futures: state.has_futures, })); - traits.push(Box::new(fuzz::FuzzTrait {})); } Mode::Dialog => { traits.push(Box::new(wave::WaveTrait { @@ -86,6 +92,7 @@ impl<'a> TraitGenerator<'a> { to_rust: true, has_replay_table: true, wrapped_labels: false, + mock_labels: true, })); traits.push(Box::new(dialog::DialogTrait {})); } diff --git a/src/traits/wave.rs b/src/traits/wave.rs index 877dff5..2d5f4de 100644 --- a/src/traits/wave.rs +++ b/src/traits/wave.rs @@ -10,6 +10,9 @@ pub struct WaveTrait { pub has_replay_table: bool, /// Label wrapped host resources by their host handles, see `proxy:conversion` pub wrapped_labels: bool, + /// Label mocked host resources by the ids the imports component gives them, see + /// `proxy:conversion` + pub mock_labels: bool, } impl Trait for WaveTrait { @@ -40,10 +43,12 @@ impl Trait for WaveTrait { }); if self.to_value { // A wrapped host resource is labeled by its host handle, which the component - // wrapping it knows, so that both generated components label it alike. + // wrapping it knows, and a mock by its id, which the component mocking it knows, + // so that both generated components label it alike. let handle = match &wit.conversion { Some(conversion) - if self.wrapped_labels && is_wrapped_module(&module_path[0]) => + if self.mock_labels + || (self.wrapped_labels && is_wrapped_module(&module_path[0])) => { let conversion = conversion.to_string(); let get_handle = format_ident!( diff --git a/src/wit_util.rs b/src/wit_util.rs index d32aa24..8d16b43 100644 --- a/src/wit_util.rs +++ b/src/wit_util.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::path::Path; use wit_component::WitPrinter; -use wit_parser::{Resolve, WorldId}; +use wit_parser::{Resolve, Type, TypeDefKind, WorldId}; // In record mode, the guest sees a copy of each imported package with its namespace // prefixed by `wrapped-` in WIT, which becomes `wrapped_` in the generated Rust modules @@ -78,6 +78,35 @@ fn is_keyword(name: &str) -> bool { ) } +/// Whether a value of `ty` has a stream or future, which the host reads or writes. +pub fn has_async_value(resolve: &Resolve, ty: &Type) -> bool { + let Type::Id(id) = ty else { + return false; + }; + let any = + |mut tys: Box + '_>| tys.any(|ty| has_async_value(resolve, ty)); + match &resolve.types[*id].kind { + TypeDefKind::Stream(_) | TypeDefKind::Future(_) => true, + TypeDefKind::Record(record) => any(Box::new(record.fields.iter().map(|f| &f.ty))), + TypeDefKind::Tuple(tuple) => any(Box::new(tuple.types.iter())), + TypeDefKind::Variant(variant) => { + any(Box::new(variant.cases.iter().filter_map(|c| c.ty.as_ref()))) + } + TypeDefKind::Result(result) => any(Box::new(result.ok.iter().chain(result.err.iter()))), + TypeDefKind::Map(key, value) => any(Box::new([key, value].into_iter())), + TypeDefKind::Option(ty) + | TypeDefKind::List(ty) + | TypeDefKind::FixedLengthList(ty, _) + | TypeDefKind::Type(ty) => has_async_value(resolve, ty), + // Handles don't contain the resource's values. + TypeDefKind::Resource + | TypeDefKind::Handle(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Unknown => false, + } +} + /// Extract WIT definitions from a wasm component and write them to `out_dir`. /// Equivalent to `wasm-tools component wit --out-dir `. pub fn extract_wit(wasm_file: &Path, out_dir: &Path) -> Result<()> { diff --git a/tests/fixtures/proxy-p3-run/guest/Cargo.lock b/tests/fixtures/proxy-p3-run/guest/Cargo.lock new file mode 100644 index 0000000..1ab4bd5 --- /dev/null +++ b/tests/fixtures/proxy-p3-run/guest/Cargo.lock @@ -0,0 +1,400 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "macro-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxy-p3-guest" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "wasm-encoder" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4495a6f85dd94c411c749303efcbc5defade0b0cbdbc0541495241ab917a9b5a" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86ff6668190d9fb70372b306b4f9fe8930f0e2f57ce72e4922bf2f117900949" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974d643b125b272607e2fff8012ea14a8328acd7621e4bafa7afb32500052b1b" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06" +dependencies = [ + "bitflags", + "futures", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e122e390603abfacd548027016cd0113a6e2bf2f8ce6bdd3658b013456ea8" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e26e13b9d972026778d2ffb7985736de116026fdc8d9464f966e12df2eb240" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a008a0d6b4840677326beb4edbeb210a2f1b7d0183fb7cc33f466b90fd7fb6" +dependencies = [ + "anyhow", + "macro-string", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078def49fdb49cab405c3006e05793b3fdaf7069126ba6b9c97d01ec8c98fe4c" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f0dc051466ffe46787721fca3d088536f158ae400be044f1b1c386ba9397ad" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/tests/fixtures/proxy-p3-run/guest/Cargo.toml b/tests/fixtures/proxy-p3-run/guest/Cargo.toml new file mode 100644 index 0000000..b071703 --- /dev/null +++ b/tests/fixtures/proxy-p3-run/guest/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "proxy-p3-guest" +version = "0.1.0" +edition = "2024" +publish = false + +[workspace] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { version = "0.63.0", features = ["async-spawn"] } diff --git a/tests/fixtures/proxy-p3-run/guest/src/lib.rs b/tests/fixtures/proxy-p3-run/guest/src/lib.rs new file mode 100644 index 0000000..3984d58 --- /dev/null +++ b/tests/fixtures/proxy-p3-run/guest/src/lib.rs @@ -0,0 +1,59 @@ +//! A main component of `tests/fixtures/proxy-p3`, which `make test-fuzz` fuzzes: it uses each +//! kind of async function, stream, future and error-context of the WIT. It tolerates whatever a +//! host may do, e.g. stop reading a stream it returned, so that a fuzzed run doesn't trap. +wit_bindgen::generate!({ + path: "../../proxy-p3", + world: "root", + generate_all, +}); + +use exports::test::p3::api::Guest; +use test::p3::host::{self, Body, ErrorCode}; +use wit_bindgen::rt::async_support::ErrorContext; +use wit_bindgen::{StreamReader, StreamResult}; + +struct Component; + +impl Guest for Component { + async fn handle(url: String) -> Result { + host::sleep(10).await; + let body = host::fetch(url).await?; + let size = body.size().await; + let (mut contents, trailers) = Body::consume(body); + let mut bytes = Vec::new(); + loop { + let (result, chunk) = contents.read(Vec::with_capacity(64)).await; + bytes.extend(chunk); + if let StreamResult::Dropped = result { + break; + } + } + let has_trailers = matches!(trailers.await, Ok(Some(_))); + if bytes.is_empty() { + host::report(ErrorContext::new("empty body")); + } + let (done_tx, done_rx) = wit_future::new(|| Ok(())); + host::finish(done_rx); + // The host may drop the future instead of reading it. + let _ = done_tx.write(Ok(())).await; + let reply = format!("size {size}, {} bytes, trailers {has_trailers}", bytes.len()); + Ok(Body::new(Some(write_stream(reply.into_bytes())))) + } + async fn serve(port: u16) -> StreamReader { + write_stream(format!("serving on {port}").into_bytes()) + } + fn echo(s: StreamReader) -> StreamReader { + s + } +} + +/// A stream of `bytes`, which the host may stop reading at any point. +fn write_stream(bytes: Vec) -> StreamReader { + let (mut tx, rx) = wit_stream::new(); + wit_bindgen::spawn_local(async move { + let _ = tx.write_all(bytes).await; + }); + rx +} + +export!(Component); diff --git a/tests/fixtures/proxy-p3/component.wit b/tests/fixtures/proxy-p3/component.wit new file mode 100644 index 0000000..c73f0a3 --- /dev/null +++ b/tests/fixtures/proxy-p3/component.wit @@ -0,0 +1,7 @@ +// The WIT of a WASI p3 main component, as `extract_wit` writes it; see `tests/fixtures/proxy`. +package root:component; + +world root { + import test:p3/host@0.1.0; + export test:p3/api@0.1.0; +} diff --git a/tests/fixtures/proxy-p3/deps/p3.wit b/tests/fixtures/proxy-p3/deps/p3.wit new file mode 100644 index 0000000..bf3a656 --- /dev/null +++ b/tests/fixtures/proxy-p3/deps/p3.wit @@ -0,0 +1,29 @@ +package test:p3@0.1.0; + +interface host { + variant error-code { + timeout, + other(option), + } + resource body { + // A stream the guest writes, and the host reads. + constructor(contents: option>); + // An async method. + size: async func() -> u64; + // A stream and a future the host writes, with a resource and a variant in the future. + consume: static func(this: body) -> tuple, future, error-code>>>; + } + fetch: async func(url: string) -> result; + sleep: async func(ms: u64); + report: func(err: error-context); + // A future the guest writes, and a function without a result. + finish: func(done: future>); +} + +interface api { + use host.{body, error-code}; + handle: async func(url: string) -> result; + // A stream the guest returns, and one the host passes to a sync export. + serve: async func(port: u16) -> stream; + echo: func(s: stream) -> stream; +} diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index 5c2b837..12c5c26 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -3,7 +3,11 @@ use bindings::*; struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { - fn start() -> () { + async fn start() -> () { + let __executors = ( + wit_bindgen::spawn_local(proxy::executor::executor::run()), + wit_bindgen::spawn_local(run_executor()), + ); loop { let idx = proxy::util::dialog::read_select( 0, @@ -264,6 +268,10 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } + proxy::executor::executor::stop(); + stop_executor(); + __executors.0.await; + __executors.1.await; } } impl ValueTyped for test::guest::api::Session { @@ -477,13 +485,19 @@ impl<'a> ValueTyped for &'a test::host::types::Counter { } impl ToValue for test::host::types::Counter { fn to_value(&self) -> Value { - let label = format!("{}-{}", "counter", self.handle()); + let label = format!( + "{}-{}", "counter", + proxy::conversion::conversion::get_handle_mock_test_host_types_counter(self) + ); Value::make_handle(label.into()) } } impl<'a> ToValue for &'a test::host::types::Counter { fn to_value(&self) -> Value { - let label = format!("borrow-{}-{}", "counter", self.handle()); + let label = format!( + "borrow-{}-{}", "counter", + proxy::conversion::conversion::get_handle_mock_test_host_types_counter(self) + ); Value::make_handle(label.into()) } } @@ -594,31 +608,17 @@ impl ToRust for Value { } impl Dialog for test::guest::api::Session { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); { fn cannot_mock() -> T { unimplemented!("cannot mock `session`, a resource of the main component") } - let _ = handle; + let _ = 0; cannot_mock() } } } impl<'a> Dialog for &'a test::guest::api::Session { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); SCOPED_ALLOC .with(|alloc| { let mut alloc = alloc.borrow_mut(); @@ -629,7 +629,7 @@ impl<'a> Dialog for &'a test::guest::api::Session { "cannot mock `session`, a resource of the main component" ) } - let _ = handle; + let _ = 0; cannot_mock() }) }) @@ -716,32 +716,18 @@ impl Dialog for test::host::types::Perms { } impl Dialog for test::host::types::Counter { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); - proxy::conversion::conversion::get_mock_test_host_types_counter(handle) + proxy::conversion::conversion::get_mock_test_host_types_counter(0) } } impl<'a> Dialog for &'a test::host::types::Counter { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); SCOPED_ALLOC .with(|alloc| { let mut alloc = alloc.borrow_mut(); alloc .alloc( proxy::conversion::conversion::get_mock_test_host_types_counter( - handle, + 0, ), ) }) @@ -750,9 +736,6 @@ impl<'a> Dialog for &'a test::host::types::Counter { trait Dialog { fn read_value(dep: u32) -> Self; } -thread_local! { - static HANDLE_ID : std::cell::RefCell < u32 > = std::cell::RefCell::new(1); -} impl Dialog for () { fn read_value(_dep: u32) -> Self { () @@ -800,15 +783,8 @@ impl Dialog for Result { } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); Self { - handle, + handle: 0, name: "mocked-resource".to_string(), } } @@ -839,3 +815,66 @@ impl_dialog_tuple!(T1, T2, T3, T4, T5); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index 0d8b5e1..d64874d 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -6,11 +6,25 @@ impl exports::proxy::conversion::conversion::Guest for Stub { fn get_mock_test_host_types_counter( handle: u32, ) -> exports::proxy::conversion::conversion::TestHostTypesCounter { + let _ = handle; exports::proxy::conversion::conversion::TestHostTypesCounter::new(MockedResource { - handle, + handle: 0, name: "counter".to_string(), }) } + fn get_handle_mock_test_host_types_counter( + x: exports::proxy::conversion::conversion::TestHostTypesCounterBorrow<'_>, + ) -> u32 { + x.get::().handle + } +} +impl exports::proxy::executor::executor::Guest for Stub { + async fn run() -> () { + run_executor().await + } + fn stop() -> () { + stop_executor() + } } impl exports::test::host::types::Guest for Stub { type Counter = MockedResource; @@ -99,12 +113,7 @@ impl exports::test::host::types::GuestCounter for MockedResource { name: "counter".to_string(), ..Dialog::read_value(0) }; - proxy::util::dialog::print( - 0, - &format!( - "ret: {}", wasm_wave::to_string(& ToValue::to_value(& ret)).unwrap() - ), - ); + PENDING_RET.with(|pending| pending.set(true)); ret } fn get(&self) -> u32 { @@ -154,6 +163,30 @@ impl exports::test::host::types::GuestCounter for MockedResource { ); ret } + unsafe fn _resource_new(val: *mut u8) -> u32 { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "[export]test:host/types@0.1.0")] + unsafe extern "C" { + #[link_name = "[resource-new]counter"] + fn new(_: *mut u8) -> i32; + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn new(_: *mut u8) -> i32 { + unreachable!() + } + let handle = unsafe { new(val) } as u32; + let mock = unsafe { &mut *(val as *mut Option) } + .as_mut() + .unwrap(); + mock.handle = handle; + if PENDING_RET.with(|pending| pending.take()) { + let ret = format!( + "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() + ); + proxy::util::dialog::print(0, &ret); + } + handle + } } impl exports::test::log0_1_0::sink::Guest for Stub { #[allow(unused_variables)] @@ -529,15 +562,8 @@ impl Dialog for exports::test::host::types::Perms { } impl Dialog for exports::test::host::types::Counter { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); exports::test::host::types::Counter::new(MockedResource { - handle, + handle: 0, name: "counter".to_string(), }) } @@ -550,9 +576,6 @@ impl<'a> Dialog for exports::test::host::types::CounterBorrow<'a> { trait Dialog { fn read_value(dep: u32) -> Self; } -thread_local! { - static HANDLE_ID : std::cell::RefCell < u32 > = std::cell::RefCell::new(1); -} impl Dialog for () { fn read_value(_dep: u32) -> Self { () @@ -600,15 +623,8 @@ impl Dialog for Result { } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { - let handle = HANDLE_ID - .with(|id| { - let mut id = id.borrow_mut(); - let current_id = *id; - *id += 1; - current_id - }); Self { - handle, + handle: 0, name: "mocked-resource".to_string(), } } @@ -639,3 +655,66 @@ impl_dialog_tuple!(T1, T2, T3, T4, T5); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/dialog/wit/component.wit b/tests/snapshots/dialog/wit/component.wit index 72362a3..b423e24 100644 --- a/tests/snapshots/dialog/wit/component.wit +++ b/tests/snapshots/dialog/wit/component.wit @@ -2,6 +2,7 @@ package component:proxy; world imports { import proxy:util/dialog; export proxy:conversion/conversion; + export proxy:executor/executor; export test:host/types@0.1.0; export test:log/sink@0.1.0; export test:log/sink@0.2.0; @@ -13,6 +14,7 @@ world tmp-exports { world exports { import proxy:util/dialog; import proxy:conversion/conversion; + import proxy:executor/executor; import test:host/types@0.1.0; import test:guest/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/snapshots/dialog/wit/deps/conversion.wit b/tests/snapshots/dialog/wit/deps/conversion.wit index 062c419..81d6bc3 100644 --- a/tests/snapshots/dialog/wit/deps/conversion.wit +++ b/tests/snapshots/dialog/wit/deps/conversion.wit @@ -2,4 +2,5 @@ package proxy:conversion; interface conversion { use test:host/types@0.1.0.{counter as test-host-types-counter}; get-mock-test-host-types-counter: func(handle: u32) -> test-host-types-counter; + get-handle-mock-test-host-types-counter: func(x: borrow) -> u32; } diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index c067aa0..ee05761 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -3,7 +3,11 @@ use bindings::*; struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { - fn start() -> () { + async fn start() -> () { + let __executors = ( + wit_bindgen::spawn_local(proxy::executor::executor::run()), + wit_bindgen::spawn_local(run_executor()), + ); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -11,9 +15,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { match idx { 1usize => { let mut __params: Vec = Vec::new(); - let p: test::guest::api::Point = u.arbitrary().unwrap(); + let p: test::guest::api::Point = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); - let c: test::guest::api::Counter = u.arbitrary().unwrap(); + let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); proxy::util::debug::print( &format!( @@ -22,10 +26,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::run(&p, &c); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 2usize => { let mut __params: Vec = Vec::new(); - let s: test::guest::api::Shape = u.arbitrary().unwrap(); + let s: test::guest::api::Shape = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); proxy::util::debug::print( &format!( @@ -34,10 +41,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::classify(&s); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 3usize => { let mut __params: Vec = Vec::new(); - let c: test::guest::api::Counter = u.arbitrary().unwrap(); + let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); proxy::util::debug::print( &format!( @@ -46,10 +56,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::begin(c); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 4usize => { let mut __params: Vec = Vec::new(); - let name: String = u.arbitrary().unwrap(); + let name: String = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); proxy::util::debug::print( @@ -59,12 +72,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::Session::new(&name); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 5usize => { let mut __params: Vec = Vec::new(); - let x: i32 = u.arbitrary().unwrap(); + let x: i32 = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); - let y: i32 = u.arbitrary().unwrap(); + let y: i32 = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); proxy::util::debug::print( &format!( @@ -73,12 +89,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::make_point(x, y); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 6usize => { let mut __params: Vec = Vec::new(); - let p: test::host::types::Perms = u.arbitrary().unwrap(); + let p: test::host::types::Perms = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); - let c: test::host::types::Color = u.arbitrary().unwrap(); + let c: test::host::types::Color = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); proxy::util::debug::print( &format!( @@ -87,10 +106,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::check(p, c); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 7usize => { let mut __params: Vec = Vec::new(); - let name: String = u.arbitrary().unwrap(); + let name: String = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); proxy::util::debug::print( @@ -100,10 +122,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::open(&name); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 8usize => { let mut __params: Vec = Vec::new(); - let start: u32 = u.arbitrary().unwrap(); + let start: u32 = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); proxy::util::debug::print( @@ -113,12 +138,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::Counter::new(start); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } 9usize => { let mut __params: Vec = Vec::new(); - let a: test::host::types::Counter = u.arbitrary().unwrap(); + let a: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); - let b: test::host::types::Counter = u.arbitrary().unwrap(); + let b: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); proxy::util::debug::print( &format!( @@ -127,6 +155,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::Counter::merge(&a, b); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } } _ => unreachable!(), } @@ -135,6 +166,10 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } + proxy::executor::executor::stop(); + stop_executor(); + __executors.0.await; + __executors.1.await; } } impl ValueTyped for test::guest::api::Session { @@ -256,13 +291,19 @@ impl<'a> ValueTyped for &'a test::host::types::Counter { } impl ToValue for test::host::types::Counter { fn to_value(&self) -> Value { - let label = format!("{}-{}", "counter", self.handle()); + let label = format!( + "{}-{}", "counter", + proxy::conversion::conversion::get_handle_mock_test_host_types_counter(self) + ); Value::make_handle(label.into()) } } impl<'a> ToValue for &'a test::host::types::Counter { fn to_value(&self) -> Value { - let label = format!("borrow-{}-{}", "counter", self.handle()); + let label = format!( + "borrow-{}-{}", "counter", + proxy::conversion::conversion::get_handle_mock_test_host_types_counter(self) + ); Value::make_handle(label.into()) } } @@ -342,22 +383,19 @@ impl ToRust for Value { MockedResource { handle, name } } } -impl Arbitrary<'_> for test::guest::api::Session { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +impl Fuzz for test::guest::api::Session { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok({ fn cannot_mock() -> T { unimplemented!("cannot mock `session`, a resource of the main component") } - let _ = 42; + let _ = 0; cannot_mock() }) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) - } } -impl<'a> Arbitrary<'_> for &'a test::guest::api::Session { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +impl<'a> Fuzz for &'a test::guest::api::Session { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { SCOPED_ALLOC .with(|alloc| { let mut alloc = alloc.borrow_mut(); @@ -369,57 +407,36 @@ impl<'a> Arbitrary<'_> for &'a test::guest::api::Session { "cannot mock `session`, a resource of the main component" ) } - let _ = 42; + let _ = 0; cannot_mock() }), ) }) } } -impl Arbitrary<'_> for test::host::types::Point { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for test::host::types::Point { + fn fuzz(u: &mut Unstructured<'_>) -> Result { Ok(test::host::types::Point { - x: u.arbitrary()?, - y: u.arbitrary()?, - label: u.arbitrary()?, + x: Fuzz::fuzz(u)?, + y: Fuzz::fuzz(u)?, + label: Fuzz::fuzz(u)?, }) } - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (0, Some(0)); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - let size = <::core::option::Option as Arbitrary>::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - res - } } -impl Arbitrary<'_> for test::host::types::Shape { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for test::host::types::Shape { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(4usize)?; match idx { - 0usize => Ok(test::host::types::Shape::Circle(u.arbitrary()?)), - 1usize => Ok(test::host::types::Shape::Rect(u.arbitrary()?)), - 2usize => Ok(test::host::types::Shape::HttpError(u.arbitrary()?)), + 0usize => Ok(test::host::types::Shape::Circle(Fuzz::fuzz(u)?)), + 1usize => Ok(test::host::types::Shape::Rect(Fuzz::fuzz(u)?)), + 2usize => Ok(test::host::types::Shape::HttpError(Fuzz::fuzz(u)?)), 3usize => Ok(test::host::types::Shape::Empty), _ => unreachable!(), } } - #[allow(unused_variables, unused_mut)] - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (1, Some(1)); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::or(res, size); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::or(res, size); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::or(res, size); - res - } } -impl Arbitrary<'_> for test::host::types::Color { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for test::host::types::Color { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(3usize)?; match idx { 0usize => Ok(test::host::types::Color::Red), @@ -428,14 +445,9 @@ impl Arbitrary<'_> for test::host::types::Color { _ => unreachable!(), } } - #[allow(unused_variables, unused_mut)] - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (1, Some(1)); - res - } } -impl Arbitrary<'_> for test::host::types::Perms { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for test::host::types::Perms { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let flag_count = u.int_in_range(0..=2usize)?; let mut res = test::host::types::Perms::empty(); let choices = [ @@ -449,20 +461,14 @@ impl Arbitrary<'_> for test::host::types::Perms { } Ok(res) } - fn size_hint(_: usize) -> (usize, Option) { - (1, Some(1 + 2usize)) - } } -impl Arbitrary<'_> for test::host::types::Counter { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { - Ok(proxy::conversion::conversion::get_mock_test_host_types_counter(42)) - } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) +impl Fuzz for test::host::types::Counter { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + Ok(proxy::conversion::conversion::get_mock_test_host_types_counter(0)) } } -impl<'a> Arbitrary<'_> for &'a test::host::types::Counter { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +impl<'a> Fuzz for &'a test::host::types::Counter { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { SCOPED_ALLOC .with(|alloc| { let mut alloc = alloc.borrow_mut(); @@ -470,7 +476,7 @@ impl<'a> Arbitrary<'_> for &'a test::host::types::Counter { alloc .alloc( proxy::conversion::conversion::get_mock_test_host_types_counter( - 42, + 0, ), ), ) @@ -479,14 +485,145 @@ impl<'a> Arbitrary<'_> for &'a test::host::types::Counter { } #[allow(unused_imports)] use arbitrary::{Arbitrary, Unstructured, Result}; -impl Arbitrary<'_> for MockedResource { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +trait Fuzz: Sized { + fn fuzz(u: &mut Unstructured<'_>) -> Result; + #[allow(dead_code)] + fn consume(self, _u: &mut Unstructured<'_>) {} +} +impl Fuzz for MockedResource { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { - handle: 42, + handle: 0, name: "mocked-resource".to_string(), }) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) +} +macro_rules! impl_fuzz_primitive { + ($($t:ty),*) => { + $(impl Fuzz for $t { fn fuzz(u : & mut Unstructured < '_ >) -> Result < Self > { + u.arbitrary() } })* + }; +} +impl_fuzz_primitive!( + u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool, char, String, () +); +impl Fuzz for Option { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Some(Fuzz::fuzz(u)?) } else { None }) + } + fn consume(self, u: &mut Unstructured<'_>) { + if let Some(x) = self { + x.consume(u); + } + } +} +impl Fuzz for Vec { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let mut res = Vec::new(); + while u.arbitrary()? { + res.push(Fuzz::fuzz(u)?); + } + Ok(res) + } + fn consume(self, u: &mut Unstructured<'_>) { + for x in self { + x.consume(u); + } + } +} +impl Fuzz for core::result::Result { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Ok(Fuzz::fuzz(u)?) } else { Err(Fuzz::fuzz(u)?) }) + } + fn consume(self, u: &mut Unstructured<'_>) { + match self { + Ok(x) => x.consume(u), + Err(x) => x.consume(u), + } } } +macro_rules! impl_fuzz_tuple { + ($($T:ident),+) => { + impl < $($T : Fuzz),+ > Fuzz for ($($T,)+) { fn fuzz(u : & mut Unstructured < '_ + >) -> Result < Self > { Ok(($($T ::fuzz(u) ?,)+)) } #[allow(non_snake_case)] fn + consume(self, u : & mut Unstructured < '_ >) { let ($($T,)+) = self; $($T + .consume(u);)+ } } + }; +} +impl_fuzz_tuple!(T1); +impl_fuzz_tuple!(T1, T2); +impl_fuzz_tuple!(T1, T2, T3); +impl_fuzz_tuple!(T1, T2, T3, T4); +impl_fuzz_tuple!(T1, T2, T3, T4, T5); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(Self::new(&String::fuzz(u)?)) + } +} +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 4e3e8d7..3e29e3f 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -6,11 +6,25 @@ impl exports::proxy::conversion::conversion::Guest for Stub { fn get_mock_test_host_types_counter( handle: u32, ) -> exports::proxy::conversion::conversion::TestHostTypesCounter { + let _ = handle; exports::proxy::conversion::conversion::TestHostTypesCounter::new(MockedResource { - handle, + handle: 0, name: "counter".to_string(), }) } + fn get_handle_mock_test_host_types_counter( + x: exports::proxy::conversion::conversion::TestHostTypesCounterBorrow<'_>, + ) -> u32 { + x.get::().handle + } +} +impl exports::proxy::executor::executor::Guest for Stub { + async fn run() -> () { + run_executor().await + } + fn stop() -> () { + stop_executor() + } } impl exports::test::host::types::Guest for Stub { type Counter = MockedResource; @@ -18,13 +32,14 @@ impl exports::test::host::types::Guest for Stub { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "test:host/types@0.1.0.make-point", __buf), + &format!( + "import: {}({})", "test:host/types@0.1.0.make-point", __params.join(",") + ), ); - __buf += "test:host/types@0.1.0.make-point"; + let __buf = __params.join(",") + "test:host/types@0.1.0.make-point"; let mut u = Unstructured::new(&__buf.as_bytes()); - let res = u.arbitrary().unwrap(); + let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::util::debug::print(&format!("ret: {}", res_str)); res @@ -36,13 +51,12 @@ impl exports::test::host::types::Guest for Stub { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "test:host/types@0.1.0.check", __buf), + &format!("import: {}({})", "test:host/types@0.1.0.check", __params.join(",")), ); - __buf += "test:host/types@0.1.0.check"; + let __buf = __params.join(",") + "test:host/types@0.1.0.check"; let mut u = Unstructured::new(&__buf.as_bytes()); - let res = u.arbitrary().unwrap(); + let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::util::debug::print(&format!("ret: {}", res_str)); res @@ -52,13 +66,12 @@ impl exports::test::host::types::Guest for Stub { ) -> ::core::result::Result { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "test:host/types@0.1.0.open", __buf), + &format!("import: {}({})", "test:host/types@0.1.0.open", __params.join(",")), ); - __buf += "test:host/types@0.1.0.open"; + let __buf = __params.join(",") + "test:host/types@0.1.0.open"; let mut u = Unstructured::new(&__buf.as_bytes()); - let res = u.arbitrary().unwrap(); + let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::util::debug::print(&format!("ret: {}", res_str)); res @@ -68,31 +81,32 @@ impl exports::test::host::types::GuestCounter for MockedResource { fn new(start: u32) -> Self { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "new test:host/types/counter@0.1.0", __buf), + &format!( + "import: {}({})", "new test:host/types/counter@0.1.0", __params.join(",") + ), ); - __buf += "new test:host/types/counter@0.1.0"; + let __buf = __params.join(",") + "new test:host/types/counter@0.1.0"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = MockedResource { name: "counter".to_string(), - ..u.arbitrary().unwrap() + ..Fuzz::fuzz(&mut u).unwrap() }; - let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + PENDING_RET.with(|pending| pending.set(true)); res } fn get(&self) -> u32 { let mut __params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "test:host/types/counter@0.1.0.get", __buf), + &format!( + "import: {}({})", "test:host/types/counter@0.1.0.get", __params.join(",") + ), ); - __buf += "test:host/types/counter@0.1.0.get"; + let __buf = __params.join(",") + "test:host/types/counter@0.1.0.get"; let mut u = Unstructured::new(&__buf.as_bytes()); - let res = u.arbitrary().unwrap(); + let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::util::debug::print(&format!("ret: {}", res_str)); res @@ -106,17 +120,43 @@ impl exports::test::host::types::GuestCounter for MockedResource { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "test:host/types/counter@0.1.0.merge", __buf), + &format!( + "import: {}({})", "test:host/types/counter@0.1.0.merge", __params + .join(",") + ), ); - __buf += "test:host/types/counter@0.1.0.merge"; + let __buf = __params.join(",") + "test:host/types/counter@0.1.0.merge"; let mut u = Unstructured::new(&__buf.as_bytes()); - let res = u.arbitrary().unwrap(); + let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::util::debug::print(&format!("ret: {}", res_str)); res } + unsafe fn _resource_new(val: *mut u8) -> u32 { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "[export]test:host/types@0.1.0")] + unsafe extern "C" { + #[link_name = "[resource-new]counter"] + fn new(_: *mut u8) -> i32; + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn new(_: *mut u8) -> i32 { + unreachable!() + } + let handle = unsafe { new(val) } as u32; + let mock = unsafe { &mut *(val as *mut Option) } + .as_mut() + .unwrap(); + mock.handle = handle; + if PENDING_RET.with(|pending| pending.take()) { + let ret = format!( + "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() + ); + proxy::util::debug::print(&ret); + } + handle + } } impl exports::test::log0_1_0::sink::Guest for Stub { #[allow(unused_variables)] @@ -126,13 +166,12 @@ impl exports::test::log0_2_0::sink::Guest for Stub { fn write(msg: String) -> bool { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); - let mut __buf = __params.join(","); proxy::util::debug::print( - &format!("import: {}({})", "test:log/sink@0.2.0.write", __buf), + &format!("import: {}({})", "test:log/sink@0.2.0.write", __params.join(",")), ); - __buf += "test:log/sink@0.2.0.write"; + let __buf = __params.join(",") + "test:log/sink@0.2.0.write"; let mut u = Unstructured::new(&__buf.as_bytes()); - let res = u.arbitrary().unwrap(); + let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::util::debug::print(&format!("ret: {}", res_str)); res @@ -324,52 +363,29 @@ impl ToRust for Value { MockedResource { handle, name } } } -impl Arbitrary<'_> for exports::test::host::types::Point { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for exports::test::host::types::Point { + fn fuzz(u: &mut Unstructured<'_>) -> Result { Ok(exports::test::host::types::Point { - x: u.arbitrary()?, - y: u.arbitrary()?, - label: u.arbitrary()?, + x: Fuzz::fuzz(u)?, + y: Fuzz::fuzz(u)?, + label: Fuzz::fuzz(u)?, }) } - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (0, Some(0)); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - let size = <::core::option::Option as Arbitrary>::size_hint(depth + 1); - res = arbitrary::size_hint::and(res, size); - res - } } -impl Arbitrary<'_> for exports::test::host::types::Shape { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for exports::test::host::types::Shape { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(4usize)?; match idx { - 0usize => Ok(exports::test::host::types::Shape::Circle(u.arbitrary()?)), - 1usize => Ok(exports::test::host::types::Shape::Rect(u.arbitrary()?)), - 2usize => Ok(exports::test::host::types::Shape::HttpError(u.arbitrary()?)), + 0usize => Ok(exports::test::host::types::Shape::Circle(Fuzz::fuzz(u)?)), + 1usize => Ok(exports::test::host::types::Shape::Rect(Fuzz::fuzz(u)?)), + 2usize => Ok(exports::test::host::types::Shape::HttpError(Fuzz::fuzz(u)?)), 3usize => Ok(exports::test::host::types::Shape::Empty), _ => unreachable!(), } } - #[allow(unused_variables, unused_mut)] - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (1, Some(1)); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::or(res, size); - let size = ::size_hint( - depth + 1, - ); - res = arbitrary::size_hint::or(res, size); - let size = ::size_hint(depth + 1); - res = arbitrary::size_hint::or(res, size); - res - } } -impl Arbitrary<'_> for exports::test::host::types::Color { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for exports::test::host::types::Color { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(3usize)?; match idx { 0usize => Ok(exports::test::host::types::Color::Red), @@ -378,14 +394,9 @@ impl Arbitrary<'_> for exports::test::host::types::Color { _ => unreachable!(), } } - #[allow(unused_variables, unused_mut)] - fn size_hint(depth: usize) -> (usize, Option) { - let mut res = (1, Some(1)); - res - } } -impl Arbitrary<'_> for exports::test::host::types::Perms { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { +impl Fuzz for exports::test::host::types::Perms { + fn fuzz(u: &mut Unstructured<'_>) -> Result { let flag_count = u.int_in_range(0..=2usize)?; let mut res = exports::test::host::types::Perms::empty(); let choices = [ @@ -399,38 +410,163 @@ impl Arbitrary<'_> for exports::test::host::types::Perms { } Ok(res) } - fn size_hint(_: usize) -> (usize, Option) { - (1, Some(1 + 2usize)) - } } -impl Arbitrary<'_> for exports::test::host::types::Counter { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +impl Fuzz for exports::test::host::types::Counter { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok( exports::test::host::types::Counter::new(MockedResource { - handle: 42, + handle: 0, name: "counter".to_string(), }), ) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) - } } -impl<'a> Arbitrary<'_> for exports::test::host::types::CounterBorrow<'a> { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +impl<'a> Fuzz for exports::test::host::types::CounterBorrow<'a> { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { unreachable!() } } #[allow(unused_imports)] use arbitrary::{Arbitrary, Unstructured, Result}; -impl Arbitrary<'_> for MockedResource { - fn arbitrary(_u: &mut Unstructured<'_>) -> Result { +trait Fuzz: Sized { + fn fuzz(u: &mut Unstructured<'_>) -> Result; + #[allow(dead_code)] + fn consume(self, _u: &mut Unstructured<'_>) {} +} +impl Fuzz for MockedResource { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { - handle: 42, + handle: 0, name: "mocked-resource".to_string(), }) } - fn size_hint(_: usize) -> (usize, Option) { - (0, Some(0)) +} +macro_rules! impl_fuzz_primitive { + ($($t:ty),*) => { + $(impl Fuzz for $t { fn fuzz(u : & mut Unstructured < '_ >) -> Result < Self > { + u.arbitrary() } })* + }; +} +impl_fuzz_primitive!( + u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool, char, String, () +); +impl Fuzz for Option { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Some(Fuzz::fuzz(u)?) } else { None }) + } + fn consume(self, u: &mut Unstructured<'_>) { + if let Some(x) = self { + x.consume(u); + } + } +} +impl Fuzz for Vec { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let mut res = Vec::new(); + while u.arbitrary()? { + res.push(Fuzz::fuzz(u)?); + } + Ok(res) + } + fn consume(self, u: &mut Unstructured<'_>) { + for x in self { + x.consume(u); + } } } +impl Fuzz for core::result::Result { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Ok(Fuzz::fuzz(u)?) } else { Err(Fuzz::fuzz(u)?) }) + } + fn consume(self, u: &mut Unstructured<'_>) { + match self { + Ok(x) => x.consume(u), + Err(x) => x.consume(u), + } + } +} +macro_rules! impl_fuzz_tuple { + ($($T:ident),+) => { + impl < $($T : Fuzz),+ > Fuzz for ($($T,)+) { fn fuzz(u : & mut Unstructured < '_ + >) -> Result < Self > { Ok(($($T ::fuzz(u) ?,)+)) } #[allow(non_snake_case)] fn + consume(self, u : & mut Unstructured < '_ >) { let ($($T,)+) = self; $($T + .consume(u);)+ } } + }; +} +impl_fuzz_tuple!(T1); +impl_fuzz_tuple!(T1, T2); +impl_fuzz_tuple!(T1, T2, T3); +impl_fuzz_tuple!(T1, T2, T3, T4); +impl_fuzz_tuple!(T1, T2, T3, T4, T5); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(Self::new(&String::fuzz(u)?)) + } +} +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/fuzz/wit/component.wit b/tests/snapshots/fuzz/wit/component.wit index 465da96..aac8ae0 100644 --- a/tests/snapshots/fuzz/wit/component.wit +++ b/tests/snapshots/fuzz/wit/component.wit @@ -2,6 +2,7 @@ package component:proxy; world imports { import proxy:util/debug; export proxy:conversion/conversion; + export proxy:executor/executor; export test:host/types@0.1.0; export test:log/sink@0.1.0; export test:log/sink@0.2.0; @@ -13,6 +14,7 @@ world tmp-exports { world exports { import proxy:util/debug; import proxy:conversion/conversion; + import proxy:executor/executor; import test:host/types@0.1.0; import test:guest/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/snapshots/fuzz/wit/deps/conversion.wit b/tests/snapshots/fuzz/wit/deps/conversion.wit index 062c419..81d6bc3 100644 --- a/tests/snapshots/fuzz/wit/deps/conversion.wit +++ b/tests/snapshots/fuzz/wit/deps/conversion.wit @@ -2,4 +2,5 @@ package proxy:conversion; interface conversion { use test:host/types@0.1.0.{counter as test-host-types-counter}; get-mock-test-host-types-counter: func(handle: u32) -> test-host-types-counter; + get-handle-mock-test-host-types-counter: func(x: borrow) -> u32; } diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs new file mode 100644 index 0000000..8d0ae7c --- /dev/null +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -0,0 +1,559 @@ +mod bindings; +use bindings::*; +struct Stub; +bindings::export!(Stub with_types_in bindings); +impl exports::proxy::recorder::start_replay::Guest for Stub { + async fn start() -> () { + let __executors = ( + wit_bindgen::spawn_local(proxy::executor::executor::run()), + wit_bindgen::spawn_local(run_executor()), + ); + let __buf = proxy::util::debug::get_random(); + let mut u = Unstructured::new(&__buf); + for _ in 0..10 { + let idx = u.int_in_range(1..=9usize).unwrap(); + match idx { + 1usize => { + let mut __params: Vec = Vec::new(); + let url: String = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/api@0.1.0.handle", __params + .join(", ") + ), + ); + let _ = test::p3::api::handle(url).await; + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 2usize => { + let mut __params: Vec = Vec::new(); + let port: u16 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&port)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/api@0.1.0.serve", __params + .join(", ") + ), + ); + Fuzz::consume(test::p3::api::serve(port).await, &mut u); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 3usize => { + let mut __params: Vec = Vec::new(); + let s: wit_bindgen::rt::async_support::StreamReader = Fuzz::fuzz( + &mut u, + ) + .unwrap(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/api@0.1.0.echo", __params + .join(", ") + ), + ); + Fuzz::consume(test::p3::api::echo(s), &mut u); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 4usize => { + let mut __params: Vec = Vec::new(); + let url: String = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/host@0.1.0.fetch", __params + .join(", ") + ), + ); + let _ = test::p3::host::fetch(url).await; + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 5usize => { + let mut __params: Vec = Vec::new(); + let ms: u64 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/host@0.1.0.sleep", __params + .join(", ") + ), + ); + let _ = test::p3::host::sleep(ms).await; + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 6usize => { + let mut __params: Vec = Vec::new(); + let err: wit_bindgen::rt::async_support::ErrorContext = Fuzz::fuzz( + &mut u, + ) + .unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&err)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/host@0.1.0.report", __params + .join(", ") + ), + ); + let _ = test::p3::host::report(err); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 7usize => { + let mut __params: Vec = Vec::new(); + let done: wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result<(), test::p3::host::ErrorCode>, + > = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/host@0.1.0.finish", __params + .join(", ") + ), + ); + let _ = test::p3::host::finish(done); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 8usize => { + let mut __params: Vec = Vec::new(); + let contents: ::core::option::Option< + wit_bindgen::rt::async_support::StreamReader, + > = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap(), + ); + proxy::util::debug::print( + &format!( + "export: {}({})", "new test:p3/host/body@0.1.0", __params + .join(", ") + ), + ); + let _ = test::p3::host::Body::new(contents); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + 9usize => { + let mut __params: Vec = Vec::new(); + let this: test::p3::host::Body = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); + proxy::util::debug::print( + &format!( + "export: {}({})", "test:p3/host/body@0.1.0.consume", __params + .join(", ") + ), + ); + Fuzz::consume(test::p3::host::Body::consume(this), &mut u); + for _ in 0..u.int_in_range(0..=3u8).unwrap() { + wit_bindgen::yield_async().await; + } + } + _ => unreachable!(), + } + SCOPED_ALLOC + .with(|alloc| { + alloc.borrow_mut().clear(); + }); + } + proxy::executor::executor::stop(); + stop_executor(); + __executors.0.await; + __executors.1.await; + } +} +impl ValueTyped for test::p3::host::ErrorCode { + #[allow(unused_imports)] + fn value_type() -> Type { + let cases = vec![ + ("timeout", None), ("other", Some(< ::core::option::Option < String > as + ValueTyped > ::value_type())) + ]; + Type::variant(cases).unwrap() + } +} +impl ToValue for test::p3::host::ErrorCode { + fn to_value(&self) -> Value { + let ty = test::p3::host::ErrorCode::value_type(); + match self { + Self::Timeout => Value::make_variant(&ty, "timeout", None), + Self::Other(e) => Value::make_variant(&ty, "other", Some(e.to_value())), + } + .unwrap() + } +} +impl ValueTyped for test::p3::host::Body { + fn value_type() -> Type { + Type::handle("body") + } +} +impl<'a> ValueTyped for &'a test::p3::host::Body { + fn value_type() -> Type { + Type::handle("body") + } +} +impl ToValue for test::p3::host::Body { + fn to_value(&self) -> Value { + let label = format!( + "{}-{}", "body", + proxy::conversion::conversion::get_handle_mock_test_p3_host_body(self) + ); + Value::make_handle(label.into()) + } +} +impl<'a> ToValue for &'a test::p3::host::Body { + fn to_value(&self) -> Value { + let label = format!( + "borrow-{}-{}", "body", + proxy::conversion::conversion::get_handle_mock_test_p3_host_body(self) + ); + Value::make_handle(label.into()) + } +} +#[allow(unused_imports)] +use wasm_wave::{ + wasm::WasmValue, value::{Value, Type, convert::{ToRust, ToValue, ValueTyped}}, +}; +use std::{alloc::Layout, cell::RefCell}; +#[allow(dead_code)] +struct ScopedAlloc { + ptrs: Vec<(*mut u8, Layout, fn(*mut u8))>, +} +thread_local! { + static SCOPED_ALLOC : RefCell < ScopedAlloc > = RefCell::new(ScopedAlloc::new()); +} +#[allow(dead_code)] +impl ScopedAlloc { + fn new() -> Self { + Self { ptrs: Vec::new() } + } + fn alloc(&mut self, value: T) -> &'static T { + let boxed = Box::new(value); + let ptr = Box::into_raw(boxed); + fn drop_ptr(ptr: *mut u8) { + drop(unsafe { Box::from_raw(ptr as *mut T) }); + } + self.ptrs.push((ptr as *mut u8, Layout::new::(), drop_ptr::)); + unsafe { &*ptr } + } + fn clear(&mut self) { + for (ptr, _layout, drop_fn) in self.ptrs.drain(..) { + drop_fn(ptr); + } + } +} +impl Drop for ScopedAlloc { + fn drop(&mut self) { + self.clear(); + } +} +#[derive(Default, Debug)] +struct MockedResource { + handle: u32, + name: String, +} +impl ValueTyped for MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for MockedResource { + fn to_value(&self) -> Value { + let label = format!("{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ValueTyped for &MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for &MockedResource { + fn to_value(&self) -> Value { + let label = format!("borrow-{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ToRust for Value { + fn to_rust(&self) -> MockedResource { + let label = self.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} +impl Fuzz for test::p3::host::ErrorCode { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let idx = u.choose_index(2usize)?; + match idx { + 0usize => Ok(test::p3::host::ErrorCode::Timeout), + 1usize => Ok(test::p3::host::ErrorCode::Other(Fuzz::fuzz(u)?)), + _ => unreachable!(), + } + } +} +impl Fuzz for test::p3::host::Body { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + Ok(proxy::conversion::conversion::get_mock_test_p3_host_body(0)) + } +} +impl<'a> Fuzz for &'a test::p3::host::Body { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + SCOPED_ALLOC + .with(|alloc| { + let mut alloc = alloc.borrow_mut(); + Ok( + alloc + .alloc( + proxy::conversion::conversion::get_mock_test_p3_host_body(0), + ), + ) + }) + } +} +#[allow(unused_imports)] +use arbitrary::{Arbitrary, Unstructured, Result}; +trait Fuzz: Sized { + fn fuzz(u: &mut Unstructured<'_>) -> Result; + #[allow(dead_code)] + fn consume(self, _u: &mut Unstructured<'_>) {} +} +impl Fuzz for MockedResource { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + Ok(Self { + handle: 0, + name: "mocked-resource".to_string(), + }) + } +} +macro_rules! impl_fuzz_primitive { + ($($t:ty),*) => { + $(impl Fuzz for $t { fn fuzz(u : & mut Unstructured < '_ >) -> Result < Self > { + u.arbitrary() } })* + }; +} +impl_fuzz_primitive!( + u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool, char, String, () +); +impl Fuzz for Option { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Some(Fuzz::fuzz(u)?) } else { None }) + } + fn consume(self, u: &mut Unstructured<'_>) { + if let Some(x) = self { + x.consume(u); + } + } +} +impl Fuzz for Vec { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let mut res = Vec::new(); + while u.arbitrary()? { + res.push(Fuzz::fuzz(u)?); + } + Ok(res) + } + fn consume(self, u: &mut Unstructured<'_>) { + for x in self { + x.consume(u); + } + } +} +impl Fuzz for core::result::Result { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Ok(Fuzz::fuzz(u)?) } else { Err(Fuzz::fuzz(u)?) }) + } + fn consume(self, u: &mut Unstructured<'_>) { + match self { + Ok(x) => x.consume(u), + Err(x) => x.consume(u), + } + } +} +macro_rules! impl_fuzz_tuple { + ($($T:ident),+) => { + impl < $($T : Fuzz),+ > Fuzz for ($($T,)+) { fn fuzz(u : & mut Unstructured < '_ + >) -> Result < Self > { Ok(($($T ::fuzz(u) ?,)+)) } #[allow(non_snake_case)] fn + consume(self, u : & mut Unstructured < '_ >) { let ($($T,)+) = self; $($T + .consume(u);)+ } } + }; +} +impl_fuzz_tuple!(T1); +impl_fuzz_tuple!(T1, T2); +impl_fuzz_tuple!(T1, T2, T3); +impl_fuzz_tuple!(T1, T2, T3, T4); +impl_fuzz_tuple!(T1, T2, T3, T4, T5); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl Fuzz for wit_bindgen::StreamReader +where + T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, +{ + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let (mut writer, reader) = wit_stream::new::(); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let chunks: Vec> = Fuzz::fuzz(u)?; + spawn(async move { + for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { + let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); + if !writer.write_all(chunk).await.is_empty() { + proxy::util::debug::print(&format!("{label} dropped by guest")); + return; + } + proxy::util::debug::print(&format!("{label}.read() = {items}")); + } + proxy::util::debug::print(&format!("{label}.read() = dropped")); + }); + Ok(reader) + } + fn consume(mut self, u: &mut Unstructured<'_>) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let reads: Option = match u.int_in_range(0..=9u8).unwrap_or(0) { + 8 => Some(u.int_in_range(1..=4).unwrap_or(1)), + 9 => Some(0), + _ => None, + }; + let capacity = u.int_in_range(1..=4096usize).unwrap_or(4096); + spawn(async move { + let mut count = 0; + while reads != Some(count) { + let (result, items) = self.read(Vec::with_capacity(capacity)).await; + if let wit_bindgen::StreamResult::Dropped = result { + proxy::util::debug::print(&format!("{label} -> dropped")); + return; + } + let items = wasm_wave::to_string(&items.to_value()).unwrap(); + proxy::util::debug::print(&format!("{label} -> {items}")); + count += 1; + } + proxy::util::debug::print(&format!("{label} dropped by host")); + }); + } +} +impl Fuzz for wit_bindgen::FutureReader +where + T: Fuzz + ToValue + ValueTyped + wit_future::FuturePayload, +{ + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let value = T::fuzz(u)?; + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + spawn(async move { + if writer.write(value).await.is_ok() { + proxy::util::debug::print(&format!("{label}.read() = {wave}")); + } else { + proxy::util::debug::print(&format!("{label} dropped by guest")); + } + }); + Ok(reader) + } + fn consume(self, u: &mut Unstructured<'_>) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let read = u.int_in_range(0..=9u8).unwrap_or(0) != 9; + spawn(async move { + if read { + let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); + proxy::util::debug::print(&format!("{label} -> {value}")); + } else { + drop(self); + proxy::util::debug::print(&format!("{label} dropped by host")); + } + }); + } +} +impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(Self::new(&String::fuzz(u)?)) + } +} +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs new file mode 100644 index 0000000..f2c9d83 --- /dev/null +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -0,0 +1,544 @@ +mod bindings; +use bindings::*; +struct Stub; +bindings::export!(Stub with_types_in bindings); +impl exports::proxy::conversion::conversion::Guest for Stub { + fn get_mock_test_p3_host_body( + handle: u32, + ) -> exports::proxy::conversion::conversion::TestP3HostBody { + let _ = handle; + exports::proxy::conversion::conversion::TestP3HostBody::new(MockedResource { + handle: 0, + name: "body".to_string(), + }) + } + fn get_handle_mock_test_p3_host_body( + x: exports::proxy::conversion::conversion::TestP3HostBodyBorrow<'_>, + ) -> u32 { + x.get::().handle + } +} +impl exports::proxy::executor::executor::Guest for Stub { + async fn run() -> () { + run_executor().await + } + fn stop() -> () { + stop_executor() + } +} +impl exports::test::p3::host::Guest for Stub { + type Body = MockedResource; + async fn fetch( + url: String, + ) -> ::core::result::Result< + exports::test::p3::host::Body, + exports::test::p3::host::ErrorCode, + > { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); + proxy::util::debug::print( + &format!("import: {}({})", "test:p3/host@0.1.0.fetch", __params.join(",")), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { + wit_bindgen::yield_async().await; + } + let __buf = __params.join(",") + "test:p3/host@0.1.0.fetch"; + let mut u = Unstructured::new(&__buf.as_bytes()); + let res = Fuzz::fuzz(&mut u).unwrap(); + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + proxy::util::debug::print(&format!("ret: {}", res_str)); + res + } + async fn sleep(ms: u64) -> () { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); + proxy::util::debug::print( + &format!("import: {}({})", "test:p3/host@0.1.0.sleep", __params.join(",")), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { + wit_bindgen::yield_async().await; + } + } + #[allow(unused_variables)] + fn report(err: wit_bindgen::rt::async_support::ErrorContext) -> () {} + fn finish( + done: wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result<(), exports::test::p3::host::ErrorCode>, + >, + ) -> () { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); + proxy::util::debug::print( + &format!("import: {}({})", "test:p3/host@0.1.0.finish", __params.join(",")), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + Fuzz::consume(done, &mut __host); + } +} +impl exports::test::p3::host::GuestBody for MockedResource { + fn new( + contents: ::core::option::Option< + wit_bindgen::rt::async_support::StreamReader, + >, + ) -> Self { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap()); + proxy::util::debug::print( + &format!("import: {}({})", "new test:p3/host/body@0.1.0", __params.join(",")), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + Fuzz::consume(contents, &mut __host); + let __buf = __params.join(",") + "new test:p3/host/body@0.1.0"; + let mut u = Unstructured::new(&__buf.as_bytes()); + let res = MockedResource { + name: "body".to_string(), + ..Fuzz::fuzz(&mut u).unwrap() + }; + PENDING_RET.with(|pending| pending.set(true)); + res + } + async fn size(&self) -> u64 { + let mut __params: Vec = vec![ + wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() + ]; + proxy::util::debug::print( + &format!( + "import: {}({})", "test:p3/host/body@0.1.0.size", __params.join(",") + ), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { + wit_bindgen::yield_async().await; + } + let __buf = __params.join(",") + "test:p3/host/body@0.1.0.size"; + let mut u = Unstructured::new(&__buf.as_bytes()); + let res = Fuzz::fuzz(&mut u).unwrap(); + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + proxy::util::debug::print(&format!("ret: {}", res_str)); + res + } + fn consume( + this: exports::test::p3::host::Body, + ) -> ( + wit_bindgen::rt::async_support::StreamReader, + wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result< + ::core::option::Option, + exports::test::p3::host::ErrorCode, + >, + >, + ) { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); + proxy::util::debug::print( + &format!( + "import: {}({})", "test:p3/host/body@0.1.0.consume", __params.join(",") + ), + ); + let __buf = __params.join(",") + "test:p3/host/body@0.1.0.consume"; + let mut u = Unstructured::new(&__buf.as_bytes()); + let res = Fuzz::fuzz(&mut u).unwrap(); + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + proxy::util::debug::print(&format!("ret: {}", res_str)); + res + } + unsafe fn _resource_new(val: *mut u8) -> u32 { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "[export]test:p3/host@0.1.0")] + unsafe extern "C" { + #[link_name = "[resource-new]body"] + fn new(_: *mut u8) -> i32; + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn new(_: *mut u8) -> i32 { + unreachable!() + } + let handle = unsafe { new(val) } as u32; + let mock = unsafe { &mut *(val as *mut Option) } + .as_mut() + .unwrap(); + mock.handle = handle; + if PENDING_RET.with(|pending| pending.take()) { + let ret = format!( + "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() + ); + proxy::util::debug::print(&ret); + } + handle + } +} +impl ValueTyped for exports::test::p3::host::ErrorCode { + #[allow(unused_imports)] + fn value_type() -> Type { + let cases = vec![ + ("timeout", None), ("other", Some(< ::core::option::Option < String > as + ValueTyped > ::value_type())) + ]; + Type::variant(cases).unwrap() + } +} +impl ToValue for exports::test::p3::host::ErrorCode { + fn to_value(&self) -> Value { + let ty = exports::test::p3::host::ErrorCode::value_type(); + match self { + Self::Timeout => Value::make_variant(&ty, "timeout", None), + Self::Other(e) => Value::make_variant(&ty, "other", Some(e.to_value())), + } + .unwrap() + } +} +impl ValueTyped for exports::test::p3::host::Body { + fn value_type() -> Type { + Type::handle("body") + } +} +impl<'a> ValueTyped for exports::test::p3::host::BodyBorrow<'a> { + fn value_type() -> Type { + Type::handle("body") + } +} +impl ToValue for exports::test::p3::host::Body { + fn to_value(&self) -> Value { + let handle = self.get::().handle; + let label = format!("{}-{}", "body", handle); + Value::make_handle(label.into()) + } +} +impl<'a> ToValue for exports::test::p3::host::BodyBorrow<'a> { + fn to_value(&self) -> Value { + let handle = self.get::().handle; + let label = format!("borrow-{}-{}", "body", handle); + Value::make_handle(label.into()) + } +} +#[allow(unused_imports)] +use wasm_wave::{ + wasm::WasmValue, value::{Value, Type, convert::{ToRust, ToValue, ValueTyped}}, +}; +use std::{alloc::Layout, cell::RefCell}; +#[allow(dead_code)] +struct ScopedAlloc { + ptrs: Vec<(*mut u8, Layout, fn(*mut u8))>, +} +thread_local! { + static SCOPED_ALLOC : RefCell < ScopedAlloc > = RefCell::new(ScopedAlloc::new()); +} +#[allow(dead_code)] +impl ScopedAlloc { + fn new() -> Self { + Self { ptrs: Vec::new() } + } + fn alloc(&mut self, value: T) -> &'static T { + let boxed = Box::new(value); + let ptr = Box::into_raw(boxed); + fn drop_ptr(ptr: *mut u8) { + drop(unsafe { Box::from_raw(ptr as *mut T) }); + } + self.ptrs.push((ptr as *mut u8, Layout::new::(), drop_ptr::)); + unsafe { &*ptr } + } + fn clear(&mut self) { + for (ptr, _layout, drop_fn) in self.ptrs.drain(..) { + drop_fn(ptr); + } + } +} +impl Drop for ScopedAlloc { + fn drop(&mut self) { + self.clear(); + } +} +#[derive(Default, Debug)] +struct MockedResource { + handle: u32, + name: String, +} +impl ValueTyped for MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for MockedResource { + fn to_value(&self) -> Value { + let label = format!("{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ValueTyped for &MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for &MockedResource { + fn to_value(&self) -> Value { + let label = format!("borrow-{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ToRust for Value { + fn to_rust(&self) -> MockedResource { + let label = self.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} +impl Fuzz for exports::test::p3::host::ErrorCode { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let idx = u.choose_index(2usize)?; + match idx { + 0usize => Ok(exports::test::p3::host::ErrorCode::Timeout), + 1usize => Ok(exports::test::p3::host::ErrorCode::Other(Fuzz::fuzz(u)?)), + _ => unreachable!(), + } + } +} +impl Fuzz for exports::test::p3::host::Body { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + Ok( + exports::test::p3::host::Body::new(MockedResource { + handle: 0, + name: "body".to_string(), + }), + ) + } +} +impl<'a> Fuzz for exports::test::p3::host::BodyBorrow<'a> { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + unreachable!() + } +} +#[allow(unused_imports)] +use arbitrary::{Arbitrary, Unstructured, Result}; +trait Fuzz: Sized { + fn fuzz(u: &mut Unstructured<'_>) -> Result; + #[allow(dead_code)] + fn consume(self, _u: &mut Unstructured<'_>) {} +} +impl Fuzz for MockedResource { + fn fuzz(_u: &mut Unstructured<'_>) -> Result { + Ok(Self { + handle: 0, + name: "mocked-resource".to_string(), + }) + } +} +macro_rules! impl_fuzz_primitive { + ($($t:ty),*) => { + $(impl Fuzz for $t { fn fuzz(u : & mut Unstructured < '_ >) -> Result < Self > { + u.arbitrary() } })* + }; +} +impl_fuzz_primitive!( + u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool, char, String, () +); +impl Fuzz for Option { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Some(Fuzz::fuzz(u)?) } else { None }) + } + fn consume(self, u: &mut Unstructured<'_>) { + if let Some(x) = self { + x.consume(u); + } + } +} +impl Fuzz for Vec { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let mut res = Vec::new(); + while u.arbitrary()? { + res.push(Fuzz::fuzz(u)?); + } + Ok(res) + } + fn consume(self, u: &mut Unstructured<'_>) { + for x in self { + x.consume(u); + } + } +} +impl Fuzz for core::result::Result { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(if u.arbitrary()? { Ok(Fuzz::fuzz(u)?) } else { Err(Fuzz::fuzz(u)?) }) + } + fn consume(self, u: &mut Unstructured<'_>) { + match self { + Ok(x) => x.consume(u), + Err(x) => x.consume(u), + } + } +} +macro_rules! impl_fuzz_tuple { + ($($T:ident),+) => { + impl < $($T : Fuzz),+ > Fuzz for ($($T,)+) { fn fuzz(u : & mut Unstructured < '_ + >) -> Result < Self > { Ok(($($T ::fuzz(u) ?,)+)) } #[allow(non_snake_case)] fn + consume(self, u : & mut Unstructured < '_ >) { let ($($T,)+) = self; $($T + .consume(u);)+ } } + }; +} +impl_fuzz_tuple!(T1); +impl_fuzz_tuple!(T1, T2); +impl_fuzz_tuple!(T1, T2, T3); +impl_fuzz_tuple!(T1, T2, T3, T4); +impl_fuzz_tuple!(T1, T2, T3, T4, T5); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl Fuzz for wit_bindgen::StreamReader +where + T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, +{ + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let (mut writer, reader) = wit_stream::new::(); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let chunks: Vec> = Fuzz::fuzz(u)?; + spawn(async move { + for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { + let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); + if !writer.write_all(chunk).await.is_empty() { + proxy::util::debug::print(&format!("{label} dropped by guest")); + return; + } + proxy::util::debug::print(&format!("{label}.read() = {items}")); + } + proxy::util::debug::print(&format!("{label}.read() = dropped")); + }); + Ok(reader) + } + fn consume(mut self, u: &mut Unstructured<'_>) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let reads: Option = match u.int_in_range(0..=9u8).unwrap_or(0) { + 8 => Some(u.int_in_range(1..=4).unwrap_or(1)), + 9 => Some(0), + _ => None, + }; + let capacity = u.int_in_range(1..=4096usize).unwrap_or(4096); + spawn(async move { + let mut count = 0; + while reads != Some(count) { + let (result, items) = self.read(Vec::with_capacity(capacity)).await; + if let wit_bindgen::StreamResult::Dropped = result { + proxy::util::debug::print(&format!("{label} -> dropped")); + return; + } + let items = wasm_wave::to_string(&items.to_value()).unwrap(); + proxy::util::debug::print(&format!("{label} -> {items}")); + count += 1; + } + proxy::util::debug::print(&format!("{label} dropped by host")); + }); + } +} +impl Fuzz for wit_bindgen::FutureReader +where + T: Fuzz + ToValue + ValueTyped + wit_future::FuturePayload, +{ + fn fuzz(u: &mut Unstructured<'_>) -> Result { + let value = T::fuzz(u)?; + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + spawn(async move { + if writer.write(value).await.is_ok() { + proxy::util::debug::print(&format!("{label}.read() = {wave}")); + } else { + proxy::util::debug::print(&format!("{label} dropped by guest")); + } + }); + Ok(reader) + } + fn consume(self, u: &mut Unstructured<'_>) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let read = u.int_in_range(0..=9u8).unwrap_or(0) != 9; + spawn(async move { + if read { + let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); + proxy::util::debug::print(&format!("{label} -> {value}")); + } else { + drop(self); + proxy::util::debug::print(&format!("{label} dropped by host")); + } + }); + } +} +impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { + fn fuzz(u: &mut Unstructured<'_>) -> Result { + Ok(Self::new(&String::fuzz(u)?)) + } +} +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/p3-fuzz/wit/component.wit b/tests/snapshots/p3-fuzz/wit/component.wit new file mode 100644 index 0000000..f9dd1c9 --- /dev/null +++ b/tests/snapshots/p3-fuzz/wit/component.wit @@ -0,0 +1,19 @@ +package component:proxy; +world imports { + import proxy:util/debug; + export proxy:conversion/conversion; + export proxy:executor/executor; + export test:p3/host@0.1.0; +} +world tmp-exports { + import test:p3/api@0.1.0; + export proxy:recorder/start-replay@0.1.0; +} +world exports { + import proxy:util/debug; + import proxy:conversion/conversion; + import proxy:executor/executor; + import test:p3/host@0.1.0; + import test:p3/api@0.1.0; + export proxy:recorder/start-replay@0.1.0; +} diff --git a/tests/snapshots/p3-fuzz/wit/deps/conversion.wit b/tests/snapshots/p3-fuzz/wit/deps/conversion.wit new file mode 100644 index 0000000..5ebb984 --- /dev/null +++ b/tests/snapshots/p3-fuzz/wit/deps/conversion.wit @@ -0,0 +1,6 @@ +package proxy:conversion; +interface conversion { + use test:p3/host@0.1.0.{body as test-p3-host-body}; + get-mock-test-p3-host-body: func(handle: u32) -> test-p3-host-body; + get-handle-mock-test-p3-host-body: func(x: borrow) -> u32; +} diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index 566c995..504648b 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -3,7 +3,11 @@ use bindings::*; struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { - fn start() -> () { + async fn start() -> () { + let __executors = ( + wit_bindgen::spawn_local(proxy::executor::executor::run()), + wit_bindgen::spawn_local(run_executor()), + ); while let Some((method, args)) = proxy::recorder::replay::replay_export() { match method.as_str() { "test:guest/api@0.1.0.run" => { @@ -187,6 +191,10 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } + proxy::executor::executor::stop(); + stop_executor(); + __executors.0.await; + __executors.1.await; } } impl ValueTyped for test::guest::api::Session { @@ -519,3 +527,62 @@ thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index cd19f28..e2ed81e 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -12,6 +12,14 @@ impl exports::proxy::conversion::conversion::Guest for Stub { }) } } +impl exports::proxy::executor::executor::Guest for Stub { + async fn run() -> () { + run_executor().await + } + fn stop() -> () { + stop_executor() + } +} impl exports::test::host::types::Guest for Stub { type Counter = MockedResource; fn make_point(x: i32, y: i32) -> exports::test::host::types::Point { @@ -445,3 +453,62 @@ thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/replay/wit/component.wit b/tests/snapshots/replay/wit/component.wit index 4d9c90f..7f21908 100644 --- a/tests/snapshots/replay/wit/component.wit +++ b/tests/snapshots/replay/wit/component.wit @@ -2,6 +2,7 @@ package component:proxy; world imports { import proxy:recorder/replay@0.1.0; export proxy:conversion/conversion; + export proxy:executor/executor; export test:host/types@0.1.0; export test:log/sink@0.1.0; export test:log/sink@0.2.0; @@ -13,6 +14,7 @@ world tmp-exports { world exports { import proxy:recorder/replay@0.1.0; import proxy:conversion/conversion; + import proxy:executor/executor; import test:host/types@0.1.0; import test:guest/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/wasi_http_p3.wasm b/tests/wasi_http_p3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8ef8d4de96c45ce285e183010335ce836504c5d5 GIT binary patch literal 1145665 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 bOdL2=8X;O5yBhZ6?Vk} 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

gH*Zyw&!hTs*^mHA`iIIeP0Z3co5 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 AvH$=8 literal 0 HcmV?d00001 From 3ff325c0b036c2f63bee51e4b462dd929538920f Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Sat, 31 Oct 2026 19:57:27 -0700 Subject: [PATCH 02/25] executor for import component only --- Makefile | 3 +- assets/executor.rs | 13 +-- assets/executor.wit | 10 ++- assets/recorder.wit | 2 +- src/ast.rs | 14 ++-- src/codegen/mod.rs | 74 ++++++++++++----- src/instrument.rs | 3 + src/tests/codegen.rs | 2 +- tests/snapshots/dialog/record_exports/lib.rs | 81 +++++-------------- tests/snapshots/dialog/record_imports/lib.rs | 2 +- tests/snapshots/dialog/wit/component.wit | 4 +- .../wit/deps/{ => conversion}/conversion.wit | 0 tests/snapshots/fuzz/record_exports/lib.rs | 81 +++++-------------- tests/snapshots/fuzz/record_imports/lib.rs | 2 +- tests/snapshots/fuzz/wit/component.wit | 4 +- .../wit/deps/{ => conversion}/conversion.wit | 0 tests/snapshots/p3-fuzz/record_exports/lib.rs | 81 +++++-------------- tests/snapshots/p3-fuzz/record_imports/lib.rs | 2 +- tests/snapshots/p3-fuzz/wit/component.wit | 4 +- .../wit/deps/{ => conversion}/conversion.wit | 0 .../wit/deps/{ => conversion}/conversion.wit | 0 tests/snapshots/replay/record_exports/lib.rs | 81 +++++-------------- tests/snapshots/replay/record_imports/lib.rs | 2 +- tests/snapshots/replay/wit/component.wit | 4 +- .../wit/deps/{ => conversion}/conversion.wit | 0 25 files changed, 173 insertions(+), 296 deletions(-) rename tests/snapshots/dialog/wit/deps/{ => conversion}/conversion.wit (100%) rename tests/snapshots/fuzz/wit/deps/{ => conversion}/conversion.wit (100%) rename tests/snapshots/p3-fuzz/wit/deps/{ => conversion}/conversion.wit (100%) rename tests/snapshots/record/wit/deps/{ => conversion}/conversion.wit (100%) rename tests/snapshots/replay/wit/deps/{ => conversion}/conversion.wit (100%) diff --git a/Makefile b/Makefile index 3f3254a..e5bbbb2 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,8 @@ test-fixture: # Dialog mode has no streams or futures yet, which the p3 component has. test-dialog: rm tests/composed.wasm || true - for wasm in $(filter-out tests/wasi_http_p3.wasm,$(wildcard tests/*.wasm)); do \ + for wasm in tests/*.wasm; do \ + [ $$wasm = tests/wasi_http_p3.wasm ] && continue; \ $(MAKE) run-dialog WASM=$$wasm || exit 1; \ done diff --git a/assets/executor.rs b/assets/executor.rs index 102a0ee..617011c 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -1,5 +1,6 @@ -// The background work of the virtualized host, e.g. writing a stream it returns, or reading one -// the guest gives it. +// The executor of the imports component, which writes and reads the virtualized host's end of +// streams and futures, e.g. writing the body stream that a mock of the sync `consume-body` +// returns, or reading the body a mock of the sync `response.new` receives. // // Streams and futures themselves are not virtualized: `stream.new`, `stream.read` and the like // are canonical built-ins, which the runtime implements, not imports. What the virtualized host @@ -11,9 +12,11 @@ // stopping partway; replay the recorded chunks; observe what the guest writes; and put mocked // resources in the values, e.g. the `fields` of a `future, error-code>>`. // -// A sync function can neither wait for a stream or future, nor spawn a task, so `spawn` queues -// the work, and `run_executor` polls it from an async task, which lives for the whole run: the -// `proxy:executor/executor.run` export, which the generated `start` calls, or `start` itself. +// A sync function can't block or spawn a task, so it queues the work with `spawn` instead, and +// `run_executor` runs it in an async task, which lives for the whole run: the +// `proxy:conversion/executor.run` export, which the `start` function of the exports component +// calls. The exports component needs no executor: its work is only made by `start`, which is +// async, so its `spawn` spawns the work on the task of `start` (see `generate_start`). type Job = std::pin::Pin>>; #[derive(Default)] struct Executor { diff --git a/assets/executor.wit b/assets/executor.wit index f15cc3b..101a241 100644 --- a/assets/executor.wit +++ b/assets/executor.wit @@ -1,8 +1,10 @@ -package proxy:executor; +package proxy:conversion; -// Runs the background work of a virtualized host, e.g. writing a stream it returns, which a sync -// function cannot wait for. The generated `imports` component exports it, and the `start` -// function of the `exports` component runs it. See `assets/executor.rs`. +// Runs the jobs of the imports component, which writes and reads the virtualized host's end of +// streams and futures, e.g. writing the body stream that a mock of the sync `consume-body` +// returns. Like the generated `conversion` interface of this package, the imports component +// exports it, for the exports component to call: its `start` function runs it. See +// `assets/executor.rs`. interface executor { run: async func(); stop: func(); diff --git a/assets/recorder.wit b/assets/recorder.wit index 8c2f6d2..7e5f890 100644 --- a/assets/recorder.wit +++ b/assets/recorder.wit @@ -12,7 +12,7 @@ interface replay { } // Async, so that it can await the main component's async exports, and run the jobs of -// `proxy:executor`. +// `proxy:conversion/executor`. interface start-replay { start: async func(); } diff --git a/src/ast.rs b/src/ast.rs index 1fa40b4..ea800b2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -44,7 +44,7 @@ impl<'a> Opt<'a> { } } /// Whether the main component's exports are driven by the generated `start`, rather than - /// by the host, which also runs `proxy:executor`. + /// by the host, which also runs `proxy:conversion/executor`. fn has_start(&self) -> bool { matches!(self.mode, Mode::Replay | Mode::Fuzz | Mode::Dialog) } @@ -73,7 +73,7 @@ impl<'a> Opt<'a> { self.push_mode_imports(&mut out); out.push_str("export proxy:conversion/conversion;\n"); if self.has_start() { - out.push_str("export proxy:executor/executor;\n"); + out.push_str("export proxy:conversion/executor;\n"); } for name in interface_names(resolve, &world.imports)? { // Don't virtualize util imports @@ -127,7 +127,7 @@ impl<'a> Opt<'a> { self.push_mode_imports(&mut out); out.push_str("import proxy:conversion/conversion;\n"); if self.has_start() { - out.push_str("import proxy:executor/executor;\n"); + out.push_str("import proxy:conversion/executor;\n"); } for name in interface_names(resolve, &world.imports)? { out.push_str(&format!("import {name};\n")); @@ -225,8 +225,10 @@ impl<'a> Opt<'a> { "deps/util.wit", include_str!("../assets/util.wit").as_bytes(), ); + // The `executor` interface of `proxy:conversion`, which `generate_conversion_wit` writes + // the rest of, in the same package directory. files.push( - "deps/executor.wit", + "deps/conversion/executor.wit", include_str!("../assets/executor.wit").as_bytes(), ); Ok(()) @@ -311,7 +313,7 @@ impl<'a> Opt<'a> { } } out.push_str("}\n"); - files.push("deps/conversion.wit", out.as_bytes()); + files.push("deps/conversion/conversion.wit", out.as_bytes()); } fn load_imports(&mut self, file: &Path) -> Result<()> { for name in import_names(file)? { @@ -335,7 +337,7 @@ impl<'a> Opt<'a> { self.need_debug = true; LinkType::Debug } - "proxy:conversion/conversion" | "proxy:executor/executor" => LinkType::Imports, + "proxy:conversion/conversion" | "proxy:conversion/executor" => LinkType::Imports, "proxy:util/dialog" => LinkType::Host, name if name.starts_with("proxy:recorder/") => LinkType::Recorder, name if matches!(self.mode, Mode::Record) => match strip_wrapped(name) { diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 3643018..7414521 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -78,7 +78,7 @@ const CONVERSION_MODULE: &str = "exports::proxy::conversion::conversion"; /// Interface whose `start` function drives the main component's exports in virtualized modes. const START_REPLAY_MODULE: &str = "exports::proxy::recorder::start_replay"; /// Interface running the background work of the virtualized host, see `assets/executor.rs`. -const EXECUTOR_MODULE: &str = "exports::proxy::executor::executor"; +const EXECUTOR_MODULE: &str = "exports::proxy::conversion::executor"; /// Generates the `lib.rs` implementing the traits of `bindings`, which wit-bindgen generated /// from `world` of `resolve`. @@ -104,12 +104,16 @@ pub fn generate( let defs = state.mock_defs(); state.output.extend(defs); } - let has_executor = state.has_executor(); + let exports_executor = state.exports_executor(); + let imports_executor = state.imports_executor(); let mut file = state.into_output_file(); - if has_executor { + if exports_executor { let executor = syn::parse_file(include_str!("../../assets/executor.rs"))?; file.items.extend(executor.items); } + if imports_executor { + file.items.extend(start_spawn_defs()); + } if use_custom_allocator { // dlmalloc with a memory source that grows by bytes, for `--page-size=1`. let alloc = syn::parse_file(include_str!("../../assets/one_byte_page_alloc.rs"))?; @@ -345,28 +349,31 @@ impl State { } res } - /// Whether the component runs `proxy:executor`: the imports component exports it, and the - /// exports component imports it. - fn has_executor(&self) -> bool { + /// Whether this is the imports component, which exports `proxy:conversion/executor` to run its + /// jobs. + fn exports_executor(&self) -> bool { + self.module_paths + .iter() + .any(|path| path.join("::") == EXECUTOR_MODULE) + } + /// Whether this is the exports component, whose `start` runs `proxy:conversion/executor`. + fn imports_executor(&self) -> bool { let import = EXECUTOR_MODULE.strip_prefix("exports::").unwrap(); self.module_paths .iter() - .any(|path| [EXECUTOR_MODULE, import].contains(&path.join("::").as_str())) + .any(|path| path.join("::") == import) } - /// The async `start` function running `body`, along with the background work of both - /// generated components (see `assets/executor.rs`), until the exports it called are done. + /// The async `start` function running `body`. The imports component's jobs run in + /// `proxy:conversion/executor` until the exports `start` called are done, and the jobs of this + /// component, which `start` made, on the task of `start` (see `start_spawn_defs`). fn generate_start(&self, sig: &Signature, body: TokenStream) -> syn::ImplItemFn { parse_quote! { #sig { - let __executors = ( - wit_bindgen::spawn_local(proxy::executor::executor::run()), - wit_bindgen::spawn_local(run_executor()), - ); + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); #body - proxy::executor::executor::stop(); - stop_executor(); - __executors.0.await; - __executors.1.await; + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; } } } @@ -379,7 +386,38 @@ impl State { } } -/// The functions of `proxy:executor/executor`, which the imports component exports, for the +/// `spawn` of the exports component, whose jobs, e.g. reading a stream an export returned, are +/// only made by `start`, which is async: it spawns them on the task of `start`, which awaits them +/// with `join_spawned`. The imports component's `spawn` queues them instead, as sync functions +/// make them (see `assets/executor.rs`). +fn start_spawn_defs() -> Vec { + let file: File = parse_quote! { + thread_local! { + static SPAWNED: std::cell::RefCell>> = + const { std::cell::RefCell::new(Vec::new()) }; + } + #[allow(dead_code)] + fn spawn(job: impl std::future::Future + 'static) { + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); + } + // Awaits the spawned jobs, including those they spawn. + async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } + } + }; + file.items +} + +/// The functions of `proxy:conversion/executor`, which the imports component exports, for the /// `start` function of the exports component to run its jobs (see `assets/executor.rs`). fn generate_executor_func(sig: &Signature) -> syn::ImplItemFn { let body = match sig.ident.to_string().as_str() { diff --git a/src/instrument.rs b/src/instrument.rs index f1974b8..54f160c 100644 --- a/src/instrument.rs +++ b/src/instrument.rs @@ -111,6 +111,8 @@ pub fn generate_sources<'a>(args: &'a InstrumentArgs, dir: &Path) -> Result(args: &'a InstrumentArgs, dir: &Path) -> Result () { - let __executors = ( - wit_bindgen::spawn_local(proxy::executor::executor::run()), - wit_bindgen::spawn_local(run_executor()), - ); + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); loop { let idx = proxy::util::dialog::read_select( 0, @@ -268,10 +265,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } - proxy::executor::executor::stop(); - stop_executor(); - __executors.0.await; - __executors.1.await; + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; } } impl ValueTyped for test::guest::api::Session { @@ -819,62 +815,23 @@ thread_local! { #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } -type Job = std::pin::Pin>>; -#[derive(Default)] -struct Executor { - queue: Vec, - stopped: bool, - waker: Option, -} thread_local! { - static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const + { std::cell::RefCell::new(Vec::new()) }; } -/// Queues `job`, from any function, sync or async. #[allow(dead_code)] fn spawn(job: impl std::future::Future + 'static) { - EXECUTOR - .with_borrow_mut(|e| { - e.queue.push(Box::pin(job)); - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); -} -/// Polls the queued jobs until `stop_executor` is called and they are all done. -#[allow(dead_code)] -async fn run_executor() { - let mut running: Vec = Vec::new(); - std::future::poll_fn(|cx| { - running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - loop { - let mut queued = EXECUTOR - .with_borrow_mut(|e| std::mem::take(&mut e.queue)); - if queued.is_empty() { - break; - } - queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - running.append(&mut queued); - } - EXECUTOR - .with_borrow_mut(|e| { - if e.stopped && running.is_empty() { - std::task::Poll::Ready(()) - } else { - e.waker = Some(cx.waker().clone()); - std::task::Poll::Pending - } - }) - }) - .await -} -#[allow(dead_code)] -fn stop_executor() { - EXECUTOR - .with_borrow_mut(|e| { - e.stopped = true; - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); +} +async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } } diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index d64874d..0add349 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -18,7 +18,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { x.get::().handle } } -impl exports::proxy::executor::executor::Guest for Stub { +impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { run_executor().await } diff --git a/tests/snapshots/dialog/wit/component.wit b/tests/snapshots/dialog/wit/component.wit index b423e24..a94abfc 100644 --- a/tests/snapshots/dialog/wit/component.wit +++ b/tests/snapshots/dialog/wit/component.wit @@ -2,7 +2,7 @@ package component:proxy; world imports { import proxy:util/dialog; export proxy:conversion/conversion; - export proxy:executor/executor; + export proxy:conversion/executor; export test:host/types@0.1.0; export test:log/sink@0.1.0; export test:log/sink@0.2.0; @@ -14,7 +14,7 @@ world tmp-exports { world exports { import proxy:util/dialog; import proxy:conversion/conversion; - import proxy:executor/executor; + import proxy:conversion/executor; import test:host/types@0.1.0; import test:guest/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/snapshots/dialog/wit/deps/conversion.wit b/tests/snapshots/dialog/wit/deps/conversion/conversion.wit similarity index 100% rename from tests/snapshots/dialog/wit/deps/conversion.wit rename to tests/snapshots/dialog/wit/deps/conversion/conversion.wit diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index ee05761..757fa26 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -4,10 +4,7 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executors = ( - wit_bindgen::spawn_local(proxy::executor::executor::run()), - wit_bindgen::spawn_local(run_executor()), - ); + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -166,10 +163,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } - proxy::executor::executor::stop(); - stop_executor(); - __executors.0.await; - __executors.1.await; + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; } } impl ValueTyped for test::guest::api::Session { @@ -568,62 +564,23 @@ thread_local! { #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } -type Job = std::pin::Pin>>; -#[derive(Default)] -struct Executor { - queue: Vec, - stopped: bool, - waker: Option, -} thread_local! { - static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const + { std::cell::RefCell::new(Vec::new()) }; } -/// Queues `job`, from any function, sync or async. #[allow(dead_code)] fn spawn(job: impl std::future::Future + 'static) { - EXECUTOR - .with_borrow_mut(|e| { - e.queue.push(Box::pin(job)); - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); -} -/// Polls the queued jobs until `stop_executor` is called and they are all done. -#[allow(dead_code)] -async fn run_executor() { - let mut running: Vec = Vec::new(); - std::future::poll_fn(|cx| { - running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - loop { - let mut queued = EXECUTOR - .with_borrow_mut(|e| std::mem::take(&mut e.queue)); - if queued.is_empty() { - break; - } - queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - running.append(&mut queued); - } - EXECUTOR - .with_borrow_mut(|e| { - if e.stopped && running.is_empty() { - std::task::Poll::Ready(()) - } else { - e.waker = Some(cx.waker().clone()); - std::task::Poll::Pending - } - }) - }) - .await -} -#[allow(dead_code)] -fn stop_executor() { - EXECUTOR - .with_borrow_mut(|e| { - e.stopped = true; - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); +} +async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } } diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 3e29e3f..de6665f 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -18,7 +18,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { x.get::().handle } } -impl exports::proxy::executor::executor::Guest for Stub { +impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { run_executor().await } diff --git a/tests/snapshots/fuzz/wit/component.wit b/tests/snapshots/fuzz/wit/component.wit index aac8ae0..fab59b2 100644 --- a/tests/snapshots/fuzz/wit/component.wit +++ b/tests/snapshots/fuzz/wit/component.wit @@ -2,7 +2,7 @@ package component:proxy; world imports { import proxy:util/debug; export proxy:conversion/conversion; - export proxy:executor/executor; + export proxy:conversion/executor; export test:host/types@0.1.0; export test:log/sink@0.1.0; export test:log/sink@0.2.0; @@ -14,7 +14,7 @@ world tmp-exports { world exports { import proxy:util/debug; import proxy:conversion/conversion; - import proxy:executor/executor; + import proxy:conversion/executor; import test:host/types@0.1.0; import test:guest/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/snapshots/fuzz/wit/deps/conversion.wit b/tests/snapshots/fuzz/wit/deps/conversion/conversion.wit similarity index 100% rename from tests/snapshots/fuzz/wit/deps/conversion.wit rename to tests/snapshots/fuzz/wit/deps/conversion/conversion.wit diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 8d0ae7c..363d08f 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -4,10 +4,7 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executors = ( - wit_bindgen::spawn_local(proxy::executor::executor::run()), - wit_bindgen::spawn_local(run_executor()), - ); + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -175,10 +172,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } - proxy::executor::executor::stop(); - stop_executor(); - __executors.0.await; - __executors.1.await; + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; } } impl ValueTyped for test::p3::host::ErrorCode { @@ -498,62 +494,23 @@ thread_local! { #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } -type Job = std::pin::Pin>>; -#[derive(Default)] -struct Executor { - queue: Vec, - stopped: bool, - waker: Option, -} thread_local! { - static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const + { std::cell::RefCell::new(Vec::new()) }; } -/// Queues `job`, from any function, sync or async. #[allow(dead_code)] fn spawn(job: impl std::future::Future + 'static) { - EXECUTOR - .with_borrow_mut(|e| { - e.queue.push(Box::pin(job)); - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); -} -/// Polls the queued jobs until `stop_executor` is called and they are all done. -#[allow(dead_code)] -async fn run_executor() { - let mut running: Vec = Vec::new(); - std::future::poll_fn(|cx| { - running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - loop { - let mut queued = EXECUTOR - .with_borrow_mut(|e| std::mem::take(&mut e.queue)); - if queued.is_empty() { - break; - } - queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - running.append(&mut queued); - } - EXECUTOR - .with_borrow_mut(|e| { - if e.stopped && running.is_empty() { - std::task::Poll::Ready(()) - } else { - e.waker = Some(cx.waker().clone()); - std::task::Poll::Pending - } - }) - }) - .await -} -#[allow(dead_code)] -fn stop_executor() { - EXECUTOR - .with_borrow_mut(|e| { - e.stopped = true; - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); +} +async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } } diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index f2c9d83..2d36377 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -18,7 +18,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { x.get::().handle } } -impl exports::proxy::executor::executor::Guest for Stub { +impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { run_executor().await } diff --git a/tests/snapshots/p3-fuzz/wit/component.wit b/tests/snapshots/p3-fuzz/wit/component.wit index f9dd1c9..558e68b 100644 --- a/tests/snapshots/p3-fuzz/wit/component.wit +++ b/tests/snapshots/p3-fuzz/wit/component.wit @@ -2,7 +2,7 @@ package component:proxy; world imports { import proxy:util/debug; export proxy:conversion/conversion; - export proxy:executor/executor; + export proxy:conversion/executor; export test:p3/host@0.1.0; } world tmp-exports { @@ -12,7 +12,7 @@ world tmp-exports { world exports { import proxy:util/debug; import proxy:conversion/conversion; - import proxy:executor/executor; + import proxy:conversion/executor; import test:p3/host@0.1.0; import test:p3/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/snapshots/p3-fuzz/wit/deps/conversion.wit b/tests/snapshots/p3-fuzz/wit/deps/conversion/conversion.wit similarity index 100% rename from tests/snapshots/p3-fuzz/wit/deps/conversion.wit rename to tests/snapshots/p3-fuzz/wit/deps/conversion/conversion.wit diff --git a/tests/snapshots/record/wit/deps/conversion.wit b/tests/snapshots/record/wit/deps/conversion/conversion.wit similarity index 100% rename from tests/snapshots/record/wit/deps/conversion.wit rename to tests/snapshots/record/wit/deps/conversion/conversion.wit diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index 504648b..7c84bbe 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -4,10 +4,7 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executors = ( - wit_bindgen::spawn_local(proxy::executor::executor::run()), - wit_bindgen::spawn_local(run_executor()), - ); + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); while let Some((method, args)) = proxy::recorder::replay::replay_export() { match method.as_str() { "test:guest/api@0.1.0.run" => { @@ -191,10 +188,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { alloc.borrow_mut().clear(); }); } - proxy::executor::executor::stop(); - stop_executor(); - __executors.0.await; - __executors.1.await; + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; } } impl ValueTyped for test::guest::api::Session { @@ -527,62 +523,23 @@ thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } -type Job = std::pin::Pin>>; -#[derive(Default)] -struct Executor { - queue: Vec, - stopped: bool, - waker: Option, -} thread_local! { - static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const + { std::cell::RefCell::new(Vec::new()) }; } -/// Queues `job`, from any function, sync or async. #[allow(dead_code)] fn spawn(job: impl std::future::Future + 'static) { - EXECUTOR - .with_borrow_mut(|e| { - e.queue.push(Box::pin(job)); - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); -} -/// Polls the queued jobs until `stop_executor` is called and they are all done. -#[allow(dead_code)] -async fn run_executor() { - let mut running: Vec = Vec::new(); - std::future::poll_fn(|cx| { - running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - loop { - let mut queued = EXECUTOR - .with_borrow_mut(|e| std::mem::take(&mut e.queue)); - if queued.is_empty() { - break; - } - queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); - running.append(&mut queued); - } - EXECUTOR - .with_borrow_mut(|e| { - if e.stopped && running.is_empty() { - std::task::Poll::Ready(()) - } else { - e.waker = Some(cx.waker().clone()); - std::task::Poll::Pending - } - }) - }) - .await -} -#[allow(dead_code)] -fn stop_executor() { - EXECUTOR - .with_borrow_mut(|e| { - e.stopped = true; - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); +} +async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } } diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index e2ed81e..c170229 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -12,7 +12,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { }) } } -impl exports::proxy::executor::executor::Guest for Stub { +impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { run_executor().await } diff --git a/tests/snapshots/replay/wit/component.wit b/tests/snapshots/replay/wit/component.wit index 7f21908..99e21ce 100644 --- a/tests/snapshots/replay/wit/component.wit +++ b/tests/snapshots/replay/wit/component.wit @@ -2,7 +2,7 @@ package component:proxy; world imports { import proxy:recorder/replay@0.1.0; export proxy:conversion/conversion; - export proxy:executor/executor; + export proxy:conversion/executor; export test:host/types@0.1.0; export test:log/sink@0.1.0; export test:log/sink@0.2.0; @@ -14,7 +14,7 @@ world tmp-exports { world exports { import proxy:recorder/replay@0.1.0; import proxy:conversion/conversion; - import proxy:executor/executor; + import proxy:conversion/executor; import test:host/types@0.1.0; import test:guest/api@0.1.0; export proxy:recorder/start-replay@0.1.0; diff --git a/tests/snapshots/replay/wit/deps/conversion.wit b/tests/snapshots/replay/wit/deps/conversion/conversion.wit similarity index 100% rename from tests/snapshots/replay/wit/deps/conversion.wit rename to tests/snapshots/replay/wit/deps/conversion/conversion.wit From d560e2f771c0bf8cde089153969184544176d766 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Sat, 31 Oct 2026 22:22:32 -0700 Subject: [PATCH 03/25] p3 for dialog --- Makefile | 4 +- assets/executor.rs | 22 + src/ast.rs | 4 +- src/codegen/dialog.rs | 38 +- src/instrument.rs | 9 +- src/run.rs | 1 + src/tests/codegen.rs | 4 + src/traits/dialog.rs | 151 +++- src/traits/mod.rs | 5 +- tests/snapshots/dialog/record_exports/lib.rs | 60 +- tests/snapshots/dialog/record_imports/lib.rs | 33 +- .../snapshots/p3-dialog/record_exports/lib.rs | 697 ++++++++++++++++++ .../snapshots/p3-dialog/record_imports/lib.rs | 620 ++++++++++++++++ tests/snapshots/p3-dialog/wit/component.wit | 19 + .../wit/deps/conversion/conversion.wit | 6 + 15 files changed, 1656 insertions(+), 17 deletions(-) create mode 100644 tests/snapshots/p3-dialog/record_exports/lib.rs create mode 100644 tests/snapshots/p3-dialog/record_imports/lib.rs create mode 100644 tests/snapshots/p3-dialog/wit/component.wit create mode 100644 tests/snapshots/p3-dialog/wit/deps/conversion/conversion.wit diff --git a/Makefile b/Makefile index e5bbbb2..4e4b155 100644 --- a/Makefile +++ b/Makefile @@ -73,13 +73,13 @@ test-fixture: $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm target/release/proxy-component run composed.wasm --invoke 'start()' --trace $(FIXTURE_OUT)/trace.out -# Dialog mode has no streams or futures yet, which the p3 component has. test-dialog: rm tests/composed.wasm || true for wasm in tests/*.wasm; do \ - [ $$wasm = tests/wasi_http_p3.wasm ] && continue; \ $(MAKE) run-dialog WASM=$$wasm || exit 1; \ done + cargo build --manifest-path $(P3_GUEST)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_GUEST_OUT) + $(MAKE) run-dialog WASM=$(P3_GUEST_WASM) run-fuzz: $(INSTRUMENT) -m fuzz $(WASM) diff --git a/assets/executor.rs b/assets/executor.rs index 617011c..2ea62ba 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -17,6 +17,28 @@ // `proxy:conversion/executor.run` export, which the `start` function of the exports component // calls. The exports component needs no executor: its work is only made by `start`, which is // async, so its `spawn` spawns the work on the task of `start` (see `generate_start`). +// +// How it runs: `spawn` pushes a job and wakes `run_executor`, through wit-bindgen's inter-task +// wakeup, as a sync mock runs in another task. `run_executor` polls the running jobs, then the +// queued ones, until no more are queued, as a job may spawn another. As the jobs are polled in +// the task of `run`, a job blocked on a stream or future joins its waitable set, so the guest's +// side of the copy wakes `run`, which polls them all again: they share one waker. `start` calls +// `stop` once the exports it called are done, and `run` returns once the jobs are too. +// +// When a value with a stream or future changes hands, e.g. an export returns a stream, the +// generated code doesn't await it. It gives it to `consume` (or `fuzz`, for one the host gives), +// which decides at once what the host does, e.g. in fuzz mode whether it reads the stream to the +// end, reads a few chunks, or drops it, and with what capacity; in dialog mode, the job asks the +// user at each step. A job then does it. When it runs is up to cooperative scheduling: first at +// the next point where its task suspends, e.g. the yields of `start` after each export call, an +// async call, or at the latest when `start` waits for the jobs at the end. Each read or write +// completes once the guest does its side. So the host's reads and writes overlap the following +// export calls, in an order the schedule decides, which is part of what fuzz mode explores. +// Awaiting the value right after the call instead would hide those orders, and deadlock a guest +// that only writes a stream in a later call, or keeps it for the rest of the run. +// +// The flip side: if the guest never finishes, or drops, a stream or future it gave the host, its +// job never ends, so neither do `run` nor `start`, and wasmtime traps with "deadlock detected". type Job = std::pin::Pin>>; #[derive(Default)] struct Executor { diff --git a/src/ast.rs b/src/ast.rs index ea800b2..9b55771 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -152,7 +152,7 @@ impl<'a> Opt<'a> { if self.need_debug { out.push_str("let debug = new import:debug { ... };\n"); } - if !self.args.use_host_recorder { + if self.args.bundles_recorder() { out.push_str("let recorder = new import:recorder { ... };\n"); } out.push_str("let imports = new import:proxy {\n"); @@ -197,7 +197,7 @@ impl<'a> Opt<'a> { fn push_external_link(&self, out: &mut Source, name: &str, link_type: &LinkType) -> bool { match link_type { LinkType::Debug => push_link(out, name, "debug", name), - LinkType::Recorder if !self.args.use_host_recorder => { + LinkType::Recorder if self.args.bundles_recorder() => { push_link(out, name, "recorder", name) } LinkType::Recorder | LinkType::Host => return true, diff --git a/src/codegen/dialog.rs b/src/codegen/dialog.rs index 184460c..c550c4c 100644 --- a/src/codegen/dialog.rs +++ b/src/codegen/dialog.rs @@ -16,8 +16,20 @@ impl State { let display_name = &wit_func.display_name; let args = extract_arg_info(sig); let ret_ty = get_return_type(&sig.output); + // The streams and futures the guest gives the host, whose reading the user decides. + let consumed: Vec<_> = args + .iter() + .zip(&wit_func.params_async) + .filter(|(arg, is_async)| **is_async && !arg.is_borrowed) + .map(|(arg, _)| &arg.ident) + .collect(); + let params = params_to_wave(kind.as_ref(), &args); + let preamble = quote! { + #params + proxy::util::dialog::print(0, &format!("import: {}({})", #display_name, __params.join(", "))); + #(Dialog::consume(#consumed, 0);)* + }; if ret_ty.is_some() { - let params = params_to_wave(kind.as_ref(), &args); let ret_name = wit_func.result.as_ref().unwrap(); // A constructor's mock has no handle yet, so `_resource_new` prints its `ret`. let (read_ret, print_ret) = match wit_func.constructor_resource() { @@ -34,14 +46,20 @@ impl State { }; parse_quote! { #sig { - #params - proxy::util::dialog::print(0, &format!("import: {}({})", #display_name, __params.join(", "))); + #preamble proxy::util::dialog::print(0, &format!("return type: {}", #ret_name)); let ret = #read_ret; #print_ret ret } } + } else if !consumed.is_empty() || wit_func.is_async { + // Async imports return at once. + parse_quote! { + #sig { + #preamble + } + } } else { parse_quote! { #[allow(unused_variables)] @@ -62,8 +80,15 @@ impl State { arg_names, arg_wit_tys, arg_tys, + ret_async, .. } = func; + // The streams and futures the guest returns, whose reading the user decides. + let ret = if *ret_async { + quote! { Dialog::consume(#call, 0); } + } else { + quote! { let _ = #call; } + }; quote! { { proxy::util::dialog::print(0, &format!("call export func {}", #display_name)); @@ -74,7 +99,12 @@ impl State { __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* proxy::util::dialog::print(0, &format!("export: {}({})", #display_name, __params.join(", "))); - let _ = #call; + #ret + // Lets the guest, and the host's background work, e.g. writing the streams + // it returned, run before the next call. + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } } }) diff --git a/src/instrument.rs b/src/instrument.rs index 54f160c..297e628 100644 --- a/src/instrument.rs +++ b/src/instrument.rs @@ -22,6 +22,13 @@ pub struct InstrumentArgs { #[arg(long)] pub one_byte_page_size: bool, } +impl InstrumentArgs { + /// Whether the composition has the bundled recorder component: only record and replay + /// modes use it, unless the host implements it. + pub fn bundles_recorder(&self) -> bool { + matches!(self.mode, Mode::Record | Mode::Replay) && !self.use_host_recorder + } +} const DEBUG_WASM: &[u8] = include_bytes!("../assets/debug.wasm"); const RECORDER_WASM: &[u8] = include_bytes!("../assets/recorder.wasm"); @@ -86,7 +93,7 @@ pub fn run(args: InstrumentArgs) -> Result<()> { .arg(&wac_path) .arg("-o") .arg(output_file); - if !args.use_host_recorder { + if args.bundles_recorder() { let wasm_path = tmp_dir.join("recorder.wasm"); fs::write(&wasm_path, recorder_wasm)?; let recorder = format!("import:recorder={}", wasm_path.display()); diff --git a/src/run.rs b/src/run.rs index d5c4a27..934c0dd 100644 --- a/src/run.rs +++ b/src/run.rs @@ -79,6 +79,7 @@ pub fn run(args: RunArgs) -> anyhow::Result<()> { .wasm_custom_page_sizes(true) // The generated `start` is async, and WASI p3 components have async exports and imports. .wasm_component_model_async(true) + .wasm_component_model_error_context(true) .concurrency_support(true) //.debug_info(true) .wasm_backtrace_details(WasmBacktraceDetails::Enable); diff --git a/src/tests/codegen.rs b/src/tests/codegen.rs index 12f9a4e..344220e 100644 --- a/src/tests/codegen.rs +++ b/src/tests/codegen.rs @@ -110,3 +110,7 @@ fn dialog() { fn p3_fuzz() { snapshot(P3_FIXTURE, "p3-fuzz", Mode::Fuzz); } +#[test] +fn p3_dialog() { + snapshot(P3_FIXTURE, "p3-dialog", Mode::Dialog); +} diff --git a/src/traits/dialog.rs b/src/traits/dialog.rs index 34c51e3..cb48afa 100644 --- a/src/traits/dialog.rs +++ b/src/traits/dialog.rs @@ -4,7 +4,12 @@ use crate::traits::{Trait, new_mock}; use quote::quote; use syn::{Item, ItemEnum, ItemStruct, parse_quote}; -pub struct DialogTrait; +pub struct DialogTrait { + /// Whether the bindings have streams, whose `Dialog` impl needs their `wit_stream` module + pub has_streams: bool, + /// Whether the bindings have futures, whose `Dialog` impl needs their `wit_future` module + pub has_futures: bool, +} impl Trait for DialogTrait { fn resource_trait( @@ -168,8 +173,13 @@ impl Trait for DialogTrait { } fn trait_defs(&self) -> Vec { let ast: syn::File = parse_quote! { - trait Dialog { + trait Dialog: Sized { + // A value the virtualized host gives the guest, which the user enters. fn read_value(dep: u32) -> Self; + // What the virtualized host does with a value the guest gives it: by default, drop it. + // Only streams and futures override it, which p2 worlds don't have. + #[allow(dead_code)] + fn consume(self, _dep: u32) {} } impl Dialog for () { fn read_value(_dep: u32) -> Self { @@ -185,6 +195,11 @@ impl Trait for DialogTrait { Some(Dialog::read_value(dep + 1)) } } + fn consume(self, dep: u32) { + if let Some(x) = self { + x.consume(dep); + } + } } impl Dialog for Vec { fn read_value(dep: u32) -> Self { @@ -200,6 +215,11 @@ impl Trait for DialogTrait { (0..len).map(|_| Dialog::read_value(dep + 1)).collect() } } + fn consume(self, dep: u32) { + for x in self { + x.consume(dep); + } + } } impl Dialog for Result { fn read_value(dep: u32) -> Self { @@ -210,6 +230,12 @@ impl Trait for DialogTrait { Err(Dialog::read_value(dep + 1)) } } + fn consume(self, dep: u32) { + match self { + Ok(x) => x.consume(dep), + Err(x) => x.consume(dep), + } + } } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { @@ -254,6 +280,11 @@ impl Trait for DialogTrait { fn read_value(dep: u32) -> Self { ($($T::read_value(dep + 1),)+) } + #[allow(non_snake_case)] + fn consume(self, dep: u32) { + let ($($T,)+) = self; + $($T.consume(dep);)+ + } } }; } @@ -266,6 +297,120 @@ impl Trait for DialogTrait { impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); }; - ast.items + let mut items = ast.items; + items.extend(self.async_defs()); + items + } +} + +impl DialogTrait { + /// `Dialog` for streams, futures and error-contexts. The bindings only have the `wit_stream` + /// and `wit_future` modules for them if the world has streams or futures. + /// + /// The virtualized host writes the streams and futures it gives the guest, and reads those + /// the guest gives it, in jobs of `assets/executor.rs`, which ask the user, one chunk at a + /// time, what to write, or whether to keep reading. + fn async_defs(&self) -> Vec { + let mut items = Vec::new(); + if self.has_streams { + let ast: syn::File = parse_quote! { + impl Dialog for wit_bindgen::StreamReader + where + T: Dialog + ToValue + ValueTyped + wit_stream::StreamPayload + 'static, + { + fn read_value(_dep: u32) -> Self { + let (mut writer, reader) = wit_stream::new::(); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + spawn(async move { + let choices = ["write a chunk".to_string(), "end the stream".to_string()]; + loop { + let prompt = format!("The host writes {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + proxy::util::dialog::print(0, &format!("{label}.read() = dropped")); + return; + } + let chunk: Vec = Dialog::read_value(1); + let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); + if !writer.write_all(chunk).await.is_empty() { + proxy::util::dialog::print(0, &format!("{label} dropped by guest")); + return; + } + proxy::util::dialog::print(0, &format!("{label}.read() = {items}")); + } + }); + reader + } + fn consume(mut self, _dep: u32) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + spawn(async move { + let choices = ["read a chunk".to_string(), "drop the stream".to_string()]; + loop { + let prompt = format!("The host reads {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + proxy::util::dialog::print(0, &format!("{label} dropped by host")); + return; + } + let (result, items) = self.read(Vec::with_capacity(4096)).await; + if let wit_bindgen::StreamResult::Dropped = result { + proxy::util::dialog::print(0, &format!("{label} -> dropped")); + return; + } + let items = wasm_wave::to_string(&items.to_value()).unwrap(); + proxy::util::dialog::print(0, &format!("{label} -> {items}")); + } + }); + } + } + }; + items.extend(ast.items); + } + if self.has_futures { + let ast: syn::File = parse_quote! { + impl Dialog for wit_bindgen::FutureReader + where + T: Dialog + ToValue + ValueTyped + wit_future::FuturePayload, + { + fn read_value(_dep: u32) -> Self { + let (writer, reader) = + wit_future::new::(|| unreachable!("the value is always written")); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + spawn(async move { + proxy::util::dialog::print(0, &format!("The host writes {label}")); + let value: T = Dialog::read_value(1); + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + if writer.write(value).await.is_ok() { + proxy::util::dialog::print(0, &format!("{label}.read() = {wave}")); + } else { + proxy::util::dialog::print(0, &format!("{label} dropped by guest")); + } + }); + reader + } + fn consume(self, _dep: u32) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + spawn(async move { + let choices = ["read the value".to_string(), "drop the future".to_string()]; + let prompt = format!("The host reads {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + drop(self); + proxy::util::dialog::print(0, &format!("{label} dropped by host")); + return; + } + let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); + proxy::util::dialog::print(0, &format!("{label} -> {value}")); + }); + } + } + }; + items.extend(ast.items); + } + items.push(parse_quote! { + impl Dialog for wit_bindgen::rt::async_support::ErrorContext { + fn read_value(dep: u32) -> Self { + Self::new(&proxy::util::dialog::read_raw_string(dep, "Enter the message of an error-context")) + } + } + }); + items } } diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 9a110a9..6877122 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -94,7 +94,10 @@ impl<'a> TraitGenerator<'a> { wrapped_labels: false, mock_labels: true, })); - traits.push(Box::new(dialog::DialogTrait {})); + traits.push(Box::new(dialog::DialogTrait { + has_streams: state.has_streams, + has_futures: state.has_futures, + })); } } TraitGenerator { state, traits } diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index 23f9bc9..dbc7f8e 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -53,6 +53,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::run(&p, &c); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 2usize => { proxy::util::dialog::print( @@ -74,6 +77,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::classify(&s); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 3usize => { proxy::util::dialog::print( @@ -95,6 +101,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::begin(c); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 4usize => { proxy::util::dialog::print( @@ -121,6 +130,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::Session::new(&name); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 5usize => { proxy::util::dialog::print( @@ -150,6 +162,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::make_point(x, y); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 6usize => { proxy::util::dialog::print( @@ -177,6 +192,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::check(p, c); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 7usize => { proxy::util::dialog::print( @@ -201,6 +219,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::open(&name); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 8usize => { proxy::util::dialog::print( @@ -225,6 +246,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::Counter::new(start); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } 9usize => { proxy::util::dialog::print( @@ -257,6 +281,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::Counter::merge(&a, b); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } } _ => unreachable!(), } @@ -729,8 +756,10 @@ impl<'a> Dialog for &'a test::host::types::Counter { }) } } -trait Dialog { +trait Dialog: Sized { fn read_value(dep: u32) -> Self; + #[allow(dead_code)] + fn consume(self, _dep: u32) {} } impl Dialog for () { fn read_value(_dep: u32) -> Self { @@ -746,6 +775,11 @@ impl Dialog for Option { ); if selection == 0 { None } else { Some(Dialog::read_value(dep + 1)) } } + fn consume(self, dep: u32) { + if let Some(x) = self { + x.consume(dep); + } + } } impl Dialog for Vec { fn read_value(dep: u32) -> Self { @@ -762,6 +796,11 @@ impl Dialog for Vec { (0..len).map(|_| Dialog::read_value(dep + 1)).collect() } } + fn consume(self, dep: u32) { + for x in self { + x.consume(dep); + } + } } impl Dialog for Result { fn read_value(dep: u32) -> Self { @@ -776,6 +815,12 @@ impl Dialog for Result { Err(Dialog::read_value(dep + 1)) } } + fn consume(self, dep: u32) { + match self { + Ok(x) => x.consume(dep), + Err(x) => x.consume(dep), + } + } } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { @@ -800,7 +845,8 @@ impl_dialog_primitive! { macro_rules! impl_dialog_tuple { ($($T:ident),+) => { impl < $($T : Dialog),+ > Dialog for ($($T,)+) { fn read_value(dep : u32) -> Self - { ($($T ::read_value(dep + 1),)+) } } + { ($($T ::read_value(dep + 1),)+) } #[allow(non_snake_case)] fn consume(self, dep + : u32) { let ($($T,)+) = self; $($T .consume(dep);)+ } } }; } impl_dialog_tuple!(T1); @@ -811,6 +857,16 @@ impl_dialog_tuple!(T1, T2, T3, T4, T5); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl Dialog for wit_bindgen::rt::async_support::ErrorContext { + fn read_value(dep: u32) -> Self { + Self::new( + &proxy::util::dialog::read_raw_string( + dep, + "Enter the message of an error-context", + ), + ) + } +} thread_local! { #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index 0add349..53fab66 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -573,8 +573,10 @@ impl<'a> Dialog for exports::test::host::types::CounterBorrow<'a> { unreachable!() } } -trait Dialog { +trait Dialog: Sized { fn read_value(dep: u32) -> Self; + #[allow(dead_code)] + fn consume(self, _dep: u32) {} } impl Dialog for () { fn read_value(_dep: u32) -> Self { @@ -590,6 +592,11 @@ impl Dialog for Option { ); if selection == 0 { None } else { Some(Dialog::read_value(dep + 1)) } } + fn consume(self, dep: u32) { + if let Some(x) = self { + x.consume(dep); + } + } } impl Dialog for Vec { fn read_value(dep: u32) -> Self { @@ -606,6 +613,11 @@ impl Dialog for Vec { (0..len).map(|_| Dialog::read_value(dep + 1)).collect() } } + fn consume(self, dep: u32) { + for x in self { + x.consume(dep); + } + } } impl Dialog for Result { fn read_value(dep: u32) -> Self { @@ -620,6 +632,12 @@ impl Dialog for Result { Err(Dialog::read_value(dep + 1)) } } + fn consume(self, dep: u32) { + match self { + Ok(x) => x.consume(dep), + Err(x) => x.consume(dep), + } + } } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { @@ -644,7 +662,8 @@ impl_dialog_primitive! { macro_rules! impl_dialog_tuple { ($($T:ident),+) => { impl < $($T : Dialog),+ > Dialog for ($($T,)+) { fn read_value(dep : u32) -> Self - { ($($T ::read_value(dep + 1),)+) } } + { ($($T ::read_value(dep + 1),)+) } #[allow(non_snake_case)] fn consume(self, dep + : u32) { let ($($T,)+) = self; $($T .consume(dep);)+ } } }; } impl_dialog_tuple!(T1); @@ -655,6 +674,16 @@ impl_dialog_tuple!(T1, T2, T3, T4, T5); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl Dialog for wit_bindgen::rt::async_support::ErrorContext { + fn read_value(dep: u32) -> Self { + Self::new( + &proxy::util::dialog::read_raw_string( + dep, + "Enter the message of an error-context", + ), + ) + } +} thread_local! { #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs new file mode 100644 index 0000000..387d7d5 --- /dev/null +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -0,0 +1,697 @@ +mod bindings; +use bindings::*; +struct Stub; +bindings::export!(Stub with_types_in bindings); +impl exports::proxy::recorder::start_replay::Guest for Stub { + async fn start() -> () { + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + loop { + let idx = proxy::util::dialog::read_select( + 0, + "Select an export function to call", + &[ + "All done".to_string(), + "test:p3/api@0.1.0.handle".to_string(), + "test:p3/api@0.1.0.serve".to_string(), + "test:p3/api@0.1.0.echo".to_string(), + "test:p3/host@0.1.0.fetch".to_string(), + "test:p3/host@0.1.0.sleep".to_string(), + "test:p3/host@0.1.0.report".to_string(), + "test:p3/host@0.1.0.finish".to_string(), + "new test:p3/host/body@0.1.0".to_string(), + "test:p3/host/body@0.1.0.consume".to_string(), + ], + ) as usize; + match idx { + 0 => break, + 1usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/api@0.1.0.handle"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!( + "provide argument for {}: {}", stringify!(url), "string" + ), + ); + let url: String = Dialog::read_value(0); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/api@0.1.0.handle", __params + .join(", ") + ), + ); + let _ = test::p3::api::handle(url).await; + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 2usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/api@0.1.0.serve"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!("provide argument for {}: {}", stringify!(port), "u16"), + ); + let port: u16 = Dialog::read_value(0); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&port)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/api@0.1.0.serve", __params + .join(", ") + ), + ); + Dialog::consume(test::p3::api::serve(port).await, 0); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 3usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/api@0.1.0.echo"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!( + "provide argument for {}: {}", stringify!(s), "stream" + ), + ); + let s: wit_bindgen::rt::async_support::StreamReader = Dialog::read_value( + 0, + ); + __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/api@0.1.0.echo", __params + .join(", ") + ), + ); + Dialog::consume(test::p3::api::echo(s), 0); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 4usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/host@0.1.0.fetch"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!( + "provide argument for {}: {}", stringify!(url), "string" + ), + ); + let url: String = Dialog::read_value(0); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/host@0.1.0.fetch", __params + .join(", ") + ), + ); + let _ = test::p3::host::fetch(url).await; + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 5usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/host@0.1.0.sleep"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!("provide argument for {}: {}", stringify!(ms), "u64"), + ); + let ms: u64 = Dialog::read_value(0); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/host@0.1.0.sleep", __params + .join(", ") + ), + ); + let _ = test::p3::host::sleep(ms).await; + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 6usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/host@0.1.0.report"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!( + "provide argument for {}: {}", stringify!(err), + "error-context" + ), + ); + let err: wit_bindgen::rt::async_support::ErrorContext = Dialog::read_value( + 0, + ); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&err)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/host@0.1.0.report", __params + .join(", ") + ), + ); + let _ = test::p3::host::report(err); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 7usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "test:p3/host@0.1.0.finish"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!( + "provide argument for {}: {}", stringify!(done), + "future>" + ), + ); + let done: wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result<(), test::p3::host::ErrorCode>, + > = Dialog::read_value(0); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/host@0.1.0.finish", __params + .join(", ") + ), + ); + let _ = test::p3::host::finish(done); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 8usize => { + proxy::util::dialog::print( + 0, + &format!("call export func {}", "new test:p3/host/body@0.1.0"), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!( + "provide argument for {}: {}", stringify!(contents), + "option>" + ), + ); + let contents: ::core::option::Option< + wit_bindgen::rt::async_support::StreamReader, + > = Dialog::read_value(0); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap(), + ); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "new test:p3/host/body@0.1.0", __params + .join(", ") + ), + ); + let _ = test::p3::host::Body::new(contents); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + 9usize => { + proxy::util::dialog::print( + 0, + &format!( + "call export func {}", "test:p3/host/body@0.1.0.consume" + ), + ); + let mut __params: Vec = Vec::new(); + proxy::util::dialog::print( + 0, + &format!("provide argument for {}: {}", stringify!(this), "body"), + ); + let this: test::p3::host::Body = Dialog::read_value(0); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "export: {}({})", "test:p3/host/body@0.1.0.consume", __params + .join(", ") + ), + ); + Dialog::consume(test::p3::host::Body::consume(this), 0); + for _ in 0..8 { + wit_bindgen::yield_async().await; + } + } + _ => unreachable!(), + } + SCOPED_ALLOC + .with(|alloc| { + alloc.borrow_mut().clear(); + }); + } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + } +} +impl ValueTyped for test::p3::host::ErrorCode { + #[allow(unused_imports)] + fn value_type() -> Type { + let cases = vec![ + ("timeout", None), ("other", Some(< ::core::option::Option < String > as + ValueTyped > ::value_type())) + ]; + Type::variant(cases).unwrap() + } +} +impl ToValue for test::p3::host::ErrorCode { + fn to_value(&self) -> Value { + let ty = test::p3::host::ErrorCode::value_type(); + match self { + Self::Timeout => Value::make_variant(&ty, "timeout", None), + Self::Other(e) => Value::make_variant(&ty, "other", Some(e.to_value())), + } + .unwrap() + } +} +impl ToRust for Value { + fn to_rust(&self) -> test::p3::host::ErrorCode { + match self.unwrap_variant() { + (ref case, None) if case == "timeout" => test::p3::host::ErrorCode::Timeout, + (ref case, Some(val)) if case == "other" => { + test::p3::host::ErrorCode::Other(val.to_rust()) + } + _ => unreachable!(), + } + } +} +impl ValueTyped for test::p3::host::Body { + fn value_type() -> Type { + Type::handle("body") + } +} +impl<'a> ValueTyped for &'a test::p3::host::Body { + fn value_type() -> Type { + Type::handle("body") + } +} +impl ToValue for test::p3::host::Body { + fn to_value(&self) -> Value { + let label = format!( + "{}-{}", "body", + proxy::conversion::conversion::get_handle_mock_test_p3_host_body(self) + ); + Value::make_handle(label.into()) + } +} +impl<'a> ToValue for &'a test::p3::host::Body { + fn to_value(&self) -> Value { + let label = format!( + "borrow-{}-{}", "body", + proxy::conversion::conversion::get_handle_mock_test_p3_host_body(self) + ); + Value::make_handle(label.into()) + } +} +impl ToRust for Value { + fn to_rust(&self) -> test::p3::host::Body { + let label = self.unwrap_handle(); + let handle = label + .strip_prefix(&format!("{}-", "body")) + .and_then(|s| s.parse::().ok()) + .expect("invalid handle label"); + proxy::conversion::conversion::get_mock_test_p3_host_body(handle) + } +} +impl<'a> ToRust<&'a test::p3::host::Body> for Value { + fn to_rust(&self) -> &'a test::p3::host::Body { + let label = self.unwrap_handle(); + let handle = label + .strip_prefix(&format!("borrow-{}-", "body")) + .and_then(|s| s.parse::().ok()) + .expect("invalid handle label"); + SCOPED_ALLOC + .with(|alloc| { + let mut alloc = alloc.borrow_mut(); + alloc + .alloc( + proxy::conversion::conversion::get_mock_test_p3_host_body(handle), + ) + }) + } +} +#[allow(unused_imports)] +use wasm_wave::{ + wasm::WasmValue, value::{Value, Type, convert::{ToRust, ToValue, ValueTyped}}, +}; +use std::{alloc::Layout, cell::RefCell}; +#[allow(dead_code)] +struct ScopedAlloc { + ptrs: Vec<(*mut u8, Layout, fn(*mut u8))>, +} +thread_local! { + static SCOPED_ALLOC : RefCell < ScopedAlloc > = RefCell::new(ScopedAlloc::new()); +} +#[allow(dead_code)] +impl ScopedAlloc { + fn new() -> Self { + Self { ptrs: Vec::new() } + } + fn alloc(&mut self, value: T) -> &'static T { + let boxed = Box::new(value); + let ptr = Box::into_raw(boxed); + fn drop_ptr(ptr: *mut u8) { + drop(unsafe { Box::from_raw(ptr as *mut T) }); + } + self.ptrs.push((ptr as *mut u8, Layout::new::(), drop_ptr::)); + unsafe { &*ptr } + } + fn clear(&mut self) { + for (ptr, _layout, drop_fn) in self.ptrs.drain(..) { + drop_fn(ptr); + } + } +} +impl Drop for ScopedAlloc { + fn drop(&mut self) { + self.clear(); + } +} +#[derive(Default, Debug)] +struct MockedResource { + handle: u32, + name: String, +} +impl ValueTyped for MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for MockedResource { + fn to_value(&self) -> Value { + let label = format!("{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ValueTyped for &MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for &MockedResource { + fn to_value(&self) -> Value { + let label = format!("borrow-{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ToRust for Value { + fn to_rust(&self) -> MockedResource { + let label = self.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} +impl Dialog for test::p3::host::ErrorCode { + fn read_value(dep: u32) -> Self { + let idx = proxy::util::dialog::read_select( + dep, + &format!("Select a variant for {}", "test:p3/host@0.1.0.error-code"), + &["timeout".to_string(), "other".to_string()], + ) as usize; + match idx { + 0usize => test::p3::host::ErrorCode::Timeout, + 1usize => test::p3::host::ErrorCode::Other(Dialog::read_value(dep + 1)), + _ => unreachable!(), + } + } +} +impl Dialog for test::p3::host::Body { + fn read_value(_dep: u32) -> Self { + proxy::conversion::conversion::get_mock_test_p3_host_body(0) + } +} +impl<'a> Dialog for &'a test::p3::host::Body { + fn read_value(_dep: u32) -> Self { + SCOPED_ALLOC + .with(|alloc| { + let mut alloc = alloc.borrow_mut(); + alloc.alloc(proxy::conversion::conversion::get_mock_test_p3_host_body(0)) + }) + } +} +trait Dialog: Sized { + fn read_value(dep: u32) -> Self; + #[allow(dead_code)] + fn consume(self, _dep: u32) {} +} +impl Dialog for () { + fn read_value(_dep: u32) -> Self { + () + } +} +impl Dialog for Option { + fn read_value(dep: u32) -> Self { + let selection = proxy::util::dialog::read_select( + dep, + "Select an option tag", + &["none".to_string(), "some".to_string()], + ); + if selection == 0 { None } else { Some(Dialog::read_value(dep + 1)) } + } + fn consume(self, dep: u32) { + if let Some(x) = self { + x.consume(dep); + } + } +} +impl Dialog for Vec { + fn read_value(dep: u32) -> Self { + use std::any::TypeId; + if TypeId::of::() == TypeId::of::() { + let hex = proxy::util::dialog::read_raw_string( + dep, + "Enter a string as list", + ); + let bytes = hex.into_bytes(); + unsafe { std::mem::transmute::, Vec>(bytes) } + } else { + let len = proxy::util::dialog::read_num(dep, "Enter the length of the list"); + (0..len).map(|_| Dialog::read_value(dep + 1)).collect() + } + } + fn consume(self, dep: u32) { + for x in self { + x.consume(dep); + } + } +} +impl Dialog for Result { + fn read_value(dep: u32) -> Self { + let selection = proxy::util::dialog::read_select( + dep, + "Select result", + &["ok".to_string(), "err".to_string()], + ); + if selection == 0 { + Ok(Dialog::read_value(dep + 1)) + } else { + Err(Dialog::read_value(dep + 1)) + } + } + fn consume(self, dep: u32) { + match self { + Ok(x) => x.consume(dep), + Err(x) => x.consume(dep), + } + } +} +impl Dialog for MockedResource { + fn read_value(_dep: u32) -> Self { + Self { + handle: 0, + name: "mocked-resource".to_string(), + } + } +} +macro_rules! impl_dialog_primitive { + ($($ty:ty => $read_fn:ident),* $(,)?) => { + $(impl Dialog for $ty { fn read_value(dep : u32) -> Self { let wave = + proxy::util::dialog:: $read_fn (dep); let ret : Value = wasm_wave::from_str(& < + Self as ValueTyped > ::value_type(), & wave).unwrap(); ret.to_rust() } })* + }; +} +impl_dialog_primitive! { + bool => read_bool, u8 => read_u8, u16 => read_u16, u32 => read_u32, u64 => read_u64, + i8 => read_s8, i16 => read_s16, i32 => read_s32, i64 => read_s64, f32 => read_f32, + f64 => read_f64, char => read_char, String => read_string, +} +macro_rules! impl_dialog_tuple { + ($($T:ident),+) => { + impl < $($T : Dialog),+ > Dialog for ($($T,)+) { fn read_value(dep : u32) -> Self + { ($($T ::read_value(dep + 1),)+) } #[allow(non_snake_case)] fn consume(self, dep + : u32) { let ($($T,)+) = self; $($T .consume(dep);)+ } } + }; +} +impl_dialog_tuple!(T1); +impl_dialog_tuple!(T1, T2); +impl_dialog_tuple!(T1, T2, T3); +impl_dialog_tuple!(T1, T2, T3, T4); +impl_dialog_tuple!(T1, T2, T3, T4, T5); +impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); +impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl Dialog for wit_bindgen::StreamReader +where + T: Dialog + ToValue + ValueTyped + wit_stream::StreamPayload + 'static, +{ + fn read_value(_dep: u32) -> Self { + let (mut writer, reader) = wit_stream::new::(); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + spawn(async move { + let choices = ["write a chunk".to_string(), "end the stream".to_string()]; + loop { + let prompt = format!("The host writes {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + proxy::util::dialog::print(0, &format!("{label}.read() = dropped")); + return; + } + let chunk: Vec = Dialog::read_value(1); + let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); + if !writer.write_all(chunk).await.is_empty() { + proxy::util::dialog::print(0, &format!("{label} dropped by guest")); + return; + } + proxy::util::dialog::print(0, &format!("{label}.read() = {items}")); + } + }); + reader + } + fn consume(mut self, _dep: u32) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + spawn(async move { + let choices = ["read a chunk".to_string(), "drop the stream".to_string()]; + loop { + let prompt = format!("The host reads {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + proxy::util::dialog::print(0, &format!("{label} dropped by host")); + return; + } + let (result, items) = self.read(Vec::with_capacity(4096)).await; + if let wit_bindgen::StreamResult::Dropped = result { + proxy::util::dialog::print(0, &format!("{label} -> dropped")); + return; + } + let items = wasm_wave::to_string(&items.to_value()).unwrap(); + proxy::util::dialog::print(0, &format!("{label} -> {items}")); + } + }); + } +} +impl Dialog for wit_bindgen::FutureReader +where + T: Dialog + ToValue + ValueTyped + wit_future::FuturePayload, +{ + fn read_value(_dep: u32) -> Self { + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + spawn(async move { + proxy::util::dialog::print(0, &format!("The host writes {label}")); + let value: T = Dialog::read_value(1); + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + if writer.write(value).await.is_ok() { + proxy::util::dialog::print(0, &format!("{label}.read() = {wave}")); + } else { + proxy::util::dialog::print(0, &format!("{label} dropped by guest")); + } + }); + reader + } + fn consume(self, _dep: u32) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + spawn(async move { + let choices = ["read the value".to_string(), "drop the future".to_string()]; + let prompt = format!("The host reads {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + drop(self); + proxy::util::dialog::print(0, &format!("{label} dropped by host")); + return; + } + let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); + proxy::util::dialog::print(0, &format!("{label} -> {value}")); + }); + } +} +impl Dialog for wit_bindgen::rt::async_support::ErrorContext { + fn read_value(dep: u32) -> Self { + Self::new( + &proxy::util::dialog::read_raw_string( + dep, + "Enter the message of an error-context", + ), + ) + } +} +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +thread_local! { + static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const + { std::cell::RefCell::new(Vec::new()) }; +} +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); +} +async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } +} diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs new file mode 100644 index 0000000..5c8d985 --- /dev/null +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -0,0 +1,620 @@ +mod bindings; +use bindings::*; +struct Stub; +bindings::export!(Stub with_types_in bindings); +impl exports::proxy::conversion::conversion::Guest for Stub { + fn get_mock_test_p3_host_body( + handle: u32, + ) -> exports::proxy::conversion::conversion::TestP3HostBody { + let _ = handle; + exports::proxy::conversion::conversion::TestP3HostBody::new(MockedResource { + handle: 0, + name: "body".to_string(), + }) + } + fn get_handle_mock_test_p3_host_body( + x: exports::proxy::conversion::conversion::TestP3HostBodyBorrow<'_>, + ) -> u32 { + x.get::().handle + } +} +impl exports::proxy::conversion::executor::Guest for Stub { + async fn run() -> () { + run_executor().await + } + fn stop() -> () { + stop_executor() + } +} +impl exports::test::p3::host::Guest for Stub { + type Body = MockedResource; + async fn fetch( + url: String, + ) -> ::core::result::Result< + exports::test::p3::host::Body, + exports::test::p3::host::ErrorCode, + > { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); + proxy::util::dialog::print( + 0, + &format!("import: {}({})", "test:p3/host@0.1.0.fetch", __params.join(", ")), + ); + proxy::util::dialog::print( + 0, + &format!("return type: {}", "result"), + ); + let ret = Dialog::read_value(0); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& ret)).unwrap() + ), + ); + ret + } + async fn sleep(ms: u64) -> () { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); + proxy::util::dialog::print( + 0, + &format!("import: {}({})", "test:p3/host@0.1.0.sleep", __params.join(", ")), + ); + } + #[allow(unused_variables)] + fn report(err: wit_bindgen::rt::async_support::ErrorContext) -> () {} + fn finish( + done: wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result<(), exports::test::p3::host::ErrorCode>, + >, + ) -> () { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); + proxy::util::dialog::print( + 0, + &format!("import: {}({})", "test:p3/host@0.1.0.finish", __params.join(", ")), + ); + Dialog::consume(done, 0); + } +} +impl exports::test::p3::host::GuestBody for MockedResource { + fn new( + contents: ::core::option::Option< + wit_bindgen::rt::async_support::StreamReader, + >, + ) -> Self { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "import: {}({})", "new test:p3/host/body@0.1.0", __params.join(", ") + ), + ); + Dialog::consume(contents, 0); + proxy::util::dialog::print(0, &format!("return type: {}", "body")); + let ret = MockedResource { + name: "body".to_string(), + ..Dialog::read_value(0) + }; + PENDING_RET.with(|pending| pending.set(true)); + ret + } + async fn size(&self) -> u64 { + let mut __params: Vec = vec![ + wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() + ]; + proxy::util::dialog::print( + 0, + &format!( + "import: {}({})", "test:p3/host/body@0.1.0.size", __params.join(", ") + ), + ); + proxy::util::dialog::print(0, &format!("return type: {}", "u64")); + let ret = Dialog::read_value(0); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& ret)).unwrap() + ), + ); + ret + } + fn consume( + this: exports::test::p3::host::Body, + ) -> ( + wit_bindgen::rt::async_support::StreamReader, + wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result< + ::core::option::Option, + exports::test::p3::host::ErrorCode, + >, + >, + ) { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); + proxy::util::dialog::print( + 0, + &format!( + "import: {}({})", "test:p3/host/body@0.1.0.consume", __params.join(", ") + ), + ); + proxy::util::dialog::print( + 0, + &format!( + "return type: {}", + "tuple, future, error-code>>>" + ), + ); + let ret = Dialog::read_value(0); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& ret)).unwrap() + ), + ); + ret + } + unsafe fn _resource_new(val: *mut u8) -> u32 { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "[export]test:p3/host@0.1.0")] + unsafe extern "C" { + #[link_name = "[resource-new]body"] + fn new(_: *mut u8) -> i32; + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn new(_: *mut u8) -> i32 { + unreachable!() + } + let handle = unsafe { new(val) } as u32; + let mock = unsafe { &mut *(val as *mut Option) } + .as_mut() + .unwrap(); + mock.handle = handle; + if PENDING_RET.with(|pending| pending.take()) { + let ret = format!( + "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() + ); + proxy::util::dialog::print(0, &ret); + } + handle + } +} +impl ValueTyped for exports::test::p3::host::ErrorCode { + #[allow(unused_imports)] + fn value_type() -> Type { + let cases = vec![ + ("timeout", None), ("other", Some(< ::core::option::Option < String > as + ValueTyped > ::value_type())) + ]; + Type::variant(cases).unwrap() + } +} +impl ToValue for exports::test::p3::host::ErrorCode { + fn to_value(&self) -> Value { + let ty = exports::test::p3::host::ErrorCode::value_type(); + match self { + Self::Timeout => Value::make_variant(&ty, "timeout", None), + Self::Other(e) => Value::make_variant(&ty, "other", Some(e.to_value())), + } + .unwrap() + } +} +impl ToRust for Value { + fn to_rust(&self) -> exports::test::p3::host::ErrorCode { + match self.unwrap_variant() { + (ref case, None) if case == "timeout" => { + exports::test::p3::host::ErrorCode::Timeout + } + (ref case, Some(val)) if case == "other" => { + exports::test::p3::host::ErrorCode::Other(val.to_rust()) + } + _ => unreachable!(), + } + } +} +impl ValueTyped for exports::test::p3::host::Body { + fn value_type() -> Type { + Type::handle("body") + } +} +impl<'a> ValueTyped for exports::test::p3::host::BodyBorrow<'a> { + fn value_type() -> Type { + Type::handle("body") + } +} +impl ToValue for exports::test::p3::host::Body { + fn to_value(&self) -> Value { + let handle = self.get::().handle; + let label = format!("{}-{}", "body", handle); + Value::make_handle(label.into()) + } +} +impl<'a> ToValue for exports::test::p3::host::BodyBorrow<'a> { + fn to_value(&self) -> Value { + let handle = self.get::().handle; + let label = format!("borrow-{}-{}", "body", handle); + Value::make_handle(label.into()) + } +} +impl ToRust for Value { + fn to_rust(&self) -> exports::test::p3::host::Body { + let label = self.unwrap_handle(); + let expect_handle = label + .strip_prefix(&format!("{}-", "body")) + .and_then(|s| s.parse::().ok()) + .expect("invalid handle label"); + let handle = exports::test::p3::host::Body::new(MockedResource { + handle: expect_handle, + name: "body".to_string(), + }); + handle + } +} +impl<'a> ToRust> for Value { + fn to_rust(&self) -> exports::test::p3::host::BodyBorrow<'a> { + unreachable!() + } +} +#[allow(unused_imports)] +use wasm_wave::{ + wasm::WasmValue, value::{Value, Type, convert::{ToRust, ToValue, ValueTyped}}, +}; +use std::{alloc::Layout, cell::RefCell}; +#[allow(dead_code)] +struct ScopedAlloc { + ptrs: Vec<(*mut u8, Layout, fn(*mut u8))>, +} +thread_local! { + static SCOPED_ALLOC : RefCell < ScopedAlloc > = RefCell::new(ScopedAlloc::new()); +} +#[allow(dead_code)] +impl ScopedAlloc { + fn new() -> Self { + Self { ptrs: Vec::new() } + } + fn alloc(&mut self, value: T) -> &'static T { + let boxed = Box::new(value); + let ptr = Box::into_raw(boxed); + fn drop_ptr(ptr: *mut u8) { + drop(unsafe { Box::from_raw(ptr as *mut T) }); + } + self.ptrs.push((ptr as *mut u8, Layout::new::(), drop_ptr::)); + unsafe { &*ptr } + } + fn clear(&mut self) { + for (ptr, _layout, drop_fn) in self.ptrs.drain(..) { + drop_fn(ptr); + } + } +} +impl Drop for ScopedAlloc { + fn drop(&mut self) { + self.clear(); + } +} +#[derive(Default, Debug)] +struct MockedResource { + handle: u32, + name: String, +} +impl ValueTyped for MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for MockedResource { + fn to_value(&self) -> Value { + let label = format!("{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ValueTyped for &MockedResource { + fn value_type() -> Type { + Type::handle("mocked-resource") + } +} +impl ToValue for &MockedResource { + fn to_value(&self) -> Value { + let label = format!("borrow-{}-{}", self.name, self.handle); + Value::make_handle(label.into()) + } +} +impl ToRust for Value { + fn to_rust(&self) -> MockedResource { + let label = self.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} +impl Dialog for exports::test::p3::host::ErrorCode { + fn read_value(dep: u32) -> Self { + let idx = proxy::util::dialog::read_select( + dep, + &format!("Select a variant for {}", "test:p3/host@0.1.0.error-code"), + &["timeout".to_string(), "other".to_string()], + ) as usize; + match idx { + 0usize => exports::test::p3::host::ErrorCode::Timeout, + 1usize => { + exports::test::p3::host::ErrorCode::Other(Dialog::read_value(dep + 1)) + } + _ => unreachable!(), + } + } +} +impl Dialog for exports::test::p3::host::Body { + fn read_value(_dep: u32) -> Self { + exports::test::p3::host::Body::new(MockedResource { + handle: 0, + name: "body".to_string(), + }) + } +} +impl<'a> Dialog for exports::test::p3::host::BodyBorrow<'a> { + fn read_value(_dep: u32) -> Self { + unreachable!() + } +} +trait Dialog: Sized { + fn read_value(dep: u32) -> Self; + #[allow(dead_code)] + fn consume(self, _dep: u32) {} +} +impl Dialog for () { + fn read_value(_dep: u32) -> Self { + () + } +} +impl Dialog for Option { + fn read_value(dep: u32) -> Self { + let selection = proxy::util::dialog::read_select( + dep, + "Select an option tag", + &["none".to_string(), "some".to_string()], + ); + if selection == 0 { None } else { Some(Dialog::read_value(dep + 1)) } + } + fn consume(self, dep: u32) { + if let Some(x) = self { + x.consume(dep); + } + } +} +impl Dialog for Vec { + fn read_value(dep: u32) -> Self { + use std::any::TypeId; + if TypeId::of::() == TypeId::of::() { + let hex = proxy::util::dialog::read_raw_string( + dep, + "Enter a string as list", + ); + let bytes = hex.into_bytes(); + unsafe { std::mem::transmute::, Vec>(bytes) } + } else { + let len = proxy::util::dialog::read_num(dep, "Enter the length of the list"); + (0..len).map(|_| Dialog::read_value(dep + 1)).collect() + } + } + fn consume(self, dep: u32) { + for x in self { + x.consume(dep); + } + } +} +impl Dialog for Result { + fn read_value(dep: u32) -> Self { + let selection = proxy::util::dialog::read_select( + dep, + "Select result", + &["ok".to_string(), "err".to_string()], + ); + if selection == 0 { + Ok(Dialog::read_value(dep + 1)) + } else { + Err(Dialog::read_value(dep + 1)) + } + } + fn consume(self, dep: u32) { + match self { + Ok(x) => x.consume(dep), + Err(x) => x.consume(dep), + } + } +} +impl Dialog for MockedResource { + fn read_value(_dep: u32) -> Self { + Self { + handle: 0, + name: "mocked-resource".to_string(), + } + } +} +macro_rules! impl_dialog_primitive { + ($($ty:ty => $read_fn:ident),* $(,)?) => { + $(impl Dialog for $ty { fn read_value(dep : u32) -> Self { let wave = + proxy::util::dialog:: $read_fn (dep); let ret : Value = wasm_wave::from_str(& < + Self as ValueTyped > ::value_type(), & wave).unwrap(); ret.to_rust() } })* + }; +} +impl_dialog_primitive! { + bool => read_bool, u8 => read_u8, u16 => read_u16, u32 => read_u32, u64 => read_u64, + i8 => read_s8, i16 => read_s16, i32 => read_s32, i64 => read_s64, f32 => read_f32, + f64 => read_f64, char => read_char, String => read_string, +} +macro_rules! impl_dialog_tuple { + ($($T:ident),+) => { + impl < $($T : Dialog),+ > Dialog for ($($T,)+) { fn read_value(dep : u32) -> Self + { ($($T ::read_value(dep + 1),)+) } #[allow(non_snake_case)] fn consume(self, dep + : u32) { let ($($T,)+) = self; $($T .consume(dep);)+ } } + }; +} +impl_dialog_tuple!(T1); +impl_dialog_tuple!(T1, T2); +impl_dialog_tuple!(T1, T2, T3); +impl_dialog_tuple!(T1, T2, T3, T4); +impl_dialog_tuple!(T1, T2, T3, T4, T5); +impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); +impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl Dialog for wit_bindgen::StreamReader +where + T: Dialog + ToValue + ValueTyped + wit_stream::StreamPayload + 'static, +{ + fn read_value(_dep: u32) -> Self { + let (mut writer, reader) = wit_stream::new::(); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + spawn(async move { + let choices = ["write a chunk".to_string(), "end the stream".to_string()]; + loop { + let prompt = format!("The host writes {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + proxy::util::dialog::print(0, &format!("{label}.read() = dropped")); + return; + } + let chunk: Vec = Dialog::read_value(1); + let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); + if !writer.write_all(chunk).await.is_empty() { + proxy::util::dialog::print(0, &format!("{label} dropped by guest")); + return; + } + proxy::util::dialog::print(0, &format!("{label}.read() = {items}")); + } + }); + reader + } + fn consume(mut self, _dep: u32) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + spawn(async move { + let choices = ["read a chunk".to_string(), "drop the stream".to_string()]; + loop { + let prompt = format!("The host reads {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + proxy::util::dialog::print(0, &format!("{label} dropped by host")); + return; + } + let (result, items) = self.read(Vec::with_capacity(4096)).await; + if let wit_bindgen::StreamResult::Dropped = result { + proxy::util::dialog::print(0, &format!("{label} -> dropped")); + return; + } + let items = wasm_wave::to_string(&items.to_value()).unwrap(); + proxy::util::dialog::print(0, &format!("{label} -> {items}")); + } + }); + } +} +impl Dialog for wit_bindgen::FutureReader +where + T: Dialog + ToValue + ValueTyped + wit_future::FuturePayload, +{ + fn read_value(_dep: u32) -> Self { + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + spawn(async move { + proxy::util::dialog::print(0, &format!("The host writes {label}")); + let value: T = Dialog::read_value(1); + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + if writer.write(value).await.is_ok() { + proxy::util::dialog::print(0, &format!("{label}.read() = {wave}")); + } else { + proxy::util::dialog::print(0, &format!("{label} dropped by guest")); + } + }); + reader + } + fn consume(self, _dep: u32) { + let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + spawn(async move { + let choices = ["read the value".to_string(), "drop the future".to_string()]; + let prompt = format!("The host reads {label}"); + if proxy::util::dialog::read_select(0, &prompt, &choices) == 1 { + drop(self); + proxy::util::dialog::print(0, &format!("{label} dropped by host")); + return; + } + let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); + proxy::util::dialog::print(0, &format!("{label} -> {value}")); + }); + } +} +impl Dialog for wit_bindgen::rt::async_support::ErrorContext { + fn read_value(dep: u32) -> Self { + Self::new( + &proxy::util::dialog::read_raw_string( + dep, + "Enter the message of an error-context", + ), + ) + } +} +thread_local! { + #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR + .with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/snapshots/p3-dialog/wit/component.wit b/tests/snapshots/p3-dialog/wit/component.wit new file mode 100644 index 0000000..6901e5d --- /dev/null +++ b/tests/snapshots/p3-dialog/wit/component.wit @@ -0,0 +1,19 @@ +package component:proxy; +world imports { + import proxy:util/dialog; + export proxy:conversion/conversion; + export proxy:conversion/executor; + export test:p3/host@0.1.0; +} +world tmp-exports { + import test:p3/api@0.1.0; + export proxy:recorder/start-replay@0.1.0; +} +world exports { + import proxy:util/dialog; + import proxy:conversion/conversion; + import proxy:conversion/executor; + import test:p3/host@0.1.0; + import test:p3/api@0.1.0; + export proxy:recorder/start-replay@0.1.0; +} diff --git a/tests/snapshots/p3-dialog/wit/deps/conversion/conversion.wit b/tests/snapshots/p3-dialog/wit/deps/conversion/conversion.wit new file mode 100644 index 0000000..5ebb984 --- /dev/null +++ b/tests/snapshots/p3-dialog/wit/deps/conversion/conversion.wit @@ -0,0 +1,6 @@ +package proxy:conversion; +interface conversion { + use test:p3/host@0.1.0.{body as test-p3-host-body}; + get-mock-test-p3-host-body: func(handle: u32) -> test-p3-host-body; + get-handle-mock-test-p3-host-body: func(x: borrow) -> u32; +} From aff66d44fd98e80548aa602457764f9d8cb291e3 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Sun, 1 Nov 2026 10:21:28 -0800 Subject: [PATCH 04/25] drain executor after each export call for dialog mode --- assets/executor.rs | 7 +- src/codegen/dialog.rs | 14 +-- src/codegen/mod.rs | 46 ++++++---- tests/snapshots/dialog/record_exports/lib.rs | 85 +++++++++++++------ tests/snapshots/dialog/record_imports/lib.rs | 3 +- .../snapshots/p3-dialog/record_exports/lib.rs | 85 +++++++++++++------ .../snapshots/p3-dialog/record_imports/lib.rs | 3 +- 7 files changed, 160 insertions(+), 83 deletions(-) diff --git a/assets/executor.rs b/assets/executor.rs index 2ea62ba..ce81095 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -23,7 +23,8 @@ // queued ones, until no more are queued, as a job may spawn another. As the jobs are polled in // the task of `run`, a job blocked on a stream or future joins its waitable set, so the guest's // side of the copy wakes `run`, which polls them all again: they share one waker. `start` calls -// `stop` once the exports it called are done, and `run` returns once the jobs are too. +// `stop` once the exports it called are done, and `run` returns once the jobs are too. In dialog +// mode, `start` does so after each export call, and calls `run` again for the next one. // // When a value with a stream or future changes hands, e.g. an export returns a stream, the // generated code doesn't await it. It gives it to `consume` (or `fuzz`, for one the host gives), @@ -31,7 +32,9 @@ // end, reads a few chunks, or drops it, and with what capacity; in dialog mode, the job asks the // user at each step. A job then does it. When it runs is up to cooperative scheduling: first at // the next point where its task suspends, e.g. the yields of `start` after each export call, an -// async call, or at the latest when `start` waits for the jobs at the end. Each read or write +// async call, or at the latest when `start` waits for the jobs: at the end in fuzz mode, and +// after each export call in dialog mode, so that the user is done with the streams and futures of +// a call before choosing the next one. Each read or write // completes once the guest does its side. So the host's reads and writes overlap the following // export calls, in an order the schedule decides, which is part of what fuzz mode explores. // Awaiting the value right after the call instead would hide those orders, and deadlock a guest diff --git a/src/codegen/dialog.rs b/src/codegen/dialog.rs index c550c4c..db33590 100644 --- a/src/codegen/dialog.rs +++ b/src/codegen/dialog.rs @@ -100,11 +100,15 @@ impl State { )* proxy::util::dialog::print(0, &format!("export: {}({})", #display_name, __params.join(", "))); #ret - // Lets the guest, and the host's background work, e.g. writing the streams - // it returned, run before the next call. - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + // The user is done with the streams and futures of this call, e.g. the + // stream it returned, before choosing the next one: the jobs of this + // component, then the imports component's, whose executor runs until then, + // as this component's jobs may need the guest, and the guest the imports + // component's jobs. + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); } } }) diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 7414521..9e358a5 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -224,7 +224,7 @@ impl State { continue; } if module_full_name == EXECUTOR_MODULE { - methods.push(syn::ImplItem::Fn(generate_executor_func(&sig))); + methods.push(syn::ImplItem::Fn(self.generate_executor_func(&sig))); continue; } let is_start = module_full_name == START_REPLAY_MODULE; @@ -365,11 +365,17 @@ impl State { } /// The async `start` function running `body`. The imports component's jobs run in /// `proxy:conversion/executor` until the exports `start` called are done, and the jobs of this - /// component, which `start` made, on the task of `start` (see `start_spawn_defs`). + /// component, which `start` made, on the task of `start` (see `start_spawn_defs`). In dialog + /// mode, `body` also waits for them after each export call (see `generate_dialog_start_func`), + /// and runs `__executor` again. fn generate_start(&self, sig: &Signature, body: TokenStream) -> syn::ImplItemFn { + let executor = match self.mode { + Mode::Dialog => quote! { mut __executor }, + _ => quote! { __executor }, + }; parse_quote! { #sig { - let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + let #executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); #body join_spawned().await; proxy::conversion::executor::stop(); @@ -377,6 +383,25 @@ impl State { } } } + /// The functions of `proxy:conversion/executor`, which the imports component exports, for the + /// `start` function of the exports component to run its jobs (see `assets/executor.rs`). + fn generate_executor_func(&self, sig: &Signature) -> syn::ImplItemFn { + let body = match (sig.ident.to_string().as_str(), &self.mode) { + // In dialog mode, `start` runs the executor again after each export call. + ("run", Mode::Dialog) => quote! { + run_executor().await; + EXECUTOR.with_borrow_mut(|e| e.stopped = false); + }, + ("run", _) => quote! { run_executor().await }, + ("stop", _) => quote! { stop_executor() }, + (name, _) => unreachable!("unexpected executor function {name}"), + }; + parse_quote! { + #sig { + #body + } + } + } fn into_output_file(self) -> File { File { items: self.output, @@ -417,21 +442,6 @@ fn start_spawn_defs() -> Vec { file.items } -/// The functions of `proxy:conversion/executor`, which the imports component exports, for the -/// `start` function of the exports component to run its jobs (see `assets/executor.rs`). -fn generate_executor_func(sig: &Signature) -> syn::ImplItemFn { - let body = match sig.ident.to_string().as_str() { - "run" => quote! { run_executor().await }, - "stop" => quote! { stop_executor() }, - name => unreachable!("unexpected executor function {name}"), - }; - parse_quote! { - #sig { - #body - } - } -} - pub fn get_proxy_path(src_path: &[String]) -> Vec { assert!(src_path.len() >= 3); let mut res = src_path.to_vec(); diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index dbc7f8e..5ea6d76 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -4,7 +4,9 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + let mut __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); loop { let idx = proxy::util::dialog::read_select( 0, @@ -53,9 +55,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::run(&p, &c); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 2usize => { proxy::util::dialog::print( @@ -77,9 +82,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::classify(&s); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 3usize => { proxy::util::dialog::print( @@ -101,9 +109,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::begin(c); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 4usize => { proxy::util::dialog::print( @@ -130,9 +141,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::guest::api::Session::new(&name); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 5usize => { proxy::util::dialog::print( @@ -162,9 +176,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::make_point(x, y); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 6usize => { proxy::util::dialog::print( @@ -192,9 +209,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::check(p, c); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 7usize => { proxy::util::dialog::print( @@ -219,9 +239,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::open(&name); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 8usize => { proxy::util::dialog::print( @@ -246,9 +269,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::Counter::new(start); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 9usize => { proxy::util::dialog::print( @@ -281,9 +307,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::host::types::Counter::merge(&a, b); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } _ => unreachable!(), } diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index 53fab66..53a84ce 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -20,7 +20,8 @@ impl exports::proxy::conversion::conversion::Guest for Stub { } impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { - run_executor().await + run_executor().await; + EXECUTOR.with_borrow_mut(|e| e.stopped = false); } fn stop() -> () { stop_executor() diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index 387d7d5..3095f74 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -4,7 +4,9 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + let mut __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); loop { let idx = proxy::util::dialog::read_select( 0, @@ -47,9 +49,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::p3::api::handle(url).await; - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 2usize => { proxy::util::dialog::print( @@ -72,9 +77,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); Dialog::consume(test::p3::api::serve(port).await, 0); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 3usize => { proxy::util::dialog::print( @@ -100,9 +108,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); Dialog::consume(test::p3::api::echo(s), 0); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 4usize => { proxy::util::dialog::print( @@ -127,9 +138,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::p3::host::fetch(url).await; - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 5usize => { proxy::util::dialog::print( @@ -152,9 +166,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::p3::host::sleep(ms).await; - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 6usize => { proxy::util::dialog::print( @@ -182,9 +199,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::p3::host::report(err); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 7usize => { proxy::util::dialog::print( @@ -212,9 +232,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::p3::host::finish(done); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 8usize => { proxy::util::dialog::print( @@ -244,9 +267,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); let _ = test::p3::host::Body::new(contents); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } 9usize => { proxy::util::dialog::print( @@ -271,9 +297,12 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ), ); Dialog::consume(test::p3::host::Body::consume(this), 0); - for _ in 0..8 { - wit_bindgen::yield_async().await; - } + join_spawned().await; + proxy::conversion::executor::stop(); + __executor.await; + __executor = wit_bindgen::spawn_local( + proxy::conversion::executor::run(), + ); } _ => unreachable!(), } diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index 5c8d985..5cfbecd 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -20,7 +20,8 @@ impl exports::proxy::conversion::conversion::Guest for Stub { } impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { - run_executor().await + run_executor().await; + EXECUTOR.with_borrow_mut(|e| e.stopped = false); } fn stop() -> () { stop_executor() From c7377c0d326f8031c0e9b00e421db0a1948b6ec1 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Sun, 1 Nov 2026 13:42:03 -0800 Subject: [PATCH 05/25] record both writer and reader handles --- Cargo.lock | 2 +- src/traits/dialog.rs | 11 +++++++++-- src/traits/fuzz.rs | 11 +++++++++-- tests/snapshots/p3-dialog/record_exports/lib.rs | 14 ++++++++++++-- tests/snapshots/p3-dialog/record_imports/lib.rs | 14 ++++++++++++-- tests/snapshots/p3-fuzz/record_exports/lib.rs | 12 ++++++++++-- tests/snapshots/p3-fuzz/record_imports/lib.rs | 12 ++++++++++-- 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce3719f..b4d5857 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2113,7 +2113,7 @@ dependencies = [ [[package]] name = "wasm-wave" version = "0.263.0" -source = "git+https://github.com/chenyan2002/wasm-tools.git?branch=extend-wave-2#7b107fc6879530c68e8c6a30f9aacc078de311b4" +source = "git+https://github.com/chenyan2002/wasm-tools.git?branch=extend-wave-2#1578e5afb7dd45cfeff6e42eed6dc49e058b28c5" dependencies = [ "logos 0.16.1", "thiserror 2.0.21", diff --git a/src/traits/dialog.rs b/src/traits/dialog.rs index cb48afa..c8a0ef4 100644 --- a/src/traits/dialog.rs +++ b/src/traits/dialog.rs @@ -320,7 +320,11 @@ impl DialogTrait { { fn read_value(_dep: u32) -> Self { let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + // The host keeps the writer, so it labels the stream for its lifetime; the + // reader moves on to the guest, which frees its handle. + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::dialog::print(0, &format!("new (writer: {label}, reader: {reader_label})")); spawn(async move { let choices = ["write a chunk".to_string(), "end the stream".to_string()]; loop { @@ -373,7 +377,10 @@ impl DialogTrait { fn read_value(_dep: u32) -> Self { let (writer, reader) = wit_future::new::(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + // As for streams, the host keeps the writer, which labels the future. + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::dialog::print(0, &format!("new (writer: {label}, reader: {reader_label})")); spawn(async move { proxy::util::dialog::print(0, &format!("The host writes {label}")); let value: T = Dialog::read_value(1); diff --git a/src/traits/fuzz.rs b/src/traits/fuzz.rs index 40b6982..ece9e8e 100644 --- a/src/traits/fuzz.rs +++ b/src/traits/fuzz.rs @@ -271,7 +271,11 @@ impl FuzzTrait { // Writes fuzzed chunks, then ends the stream. fn fuzz(u: &mut Unstructured<'_>) -> Result { let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + // The host keeps the writer, so it labels the stream for its lifetime; the + // reader moves on to the guest, which frees its handle. + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::debug::print(&format!("new (writer: {label}, reader: {reader_label})")); let chunks: Vec> = Fuzz::fuzz(u)?; spawn(async move { for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { @@ -326,7 +330,10 @@ impl FuzzTrait { let value = T::fuzz(u)?; let (writer, reader) = wit_future::new::(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + // As for streams, the host keeps the writer, which labels the future. + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::debug::print(&format!("new (writer: {label}, reader: {reader_label})")); let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { if writer.write(value).await.is_ok() { diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index 3095f74..c05cec1 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -613,7 +613,12 @@ where { fn read_value(_dep: u32) -> Self { let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::dialog::print( + 0, + &format!("new (writer: {label}, reader: {reader_label})"), + ); spawn(async move { let choices = ["write a chunk".to_string(), "end the stream".to_string()]; loop { @@ -662,7 +667,12 @@ where let (writer, reader) = wit_future::new::< T, >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::dialog::print( + 0, + &format!("new (writer: {label}, reader: {reader_label})"), + ); spawn(async move { proxy::util::dialog::print(0, &format!("The host writes {label}")); let value: T = Dialog::read_value(1); diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index 5cfbecd..b39a9d1 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -469,7 +469,12 @@ where { fn read_value(_dep: u32) -> Self { let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::dialog::print( + 0, + &format!("new (writer: {label}, reader: {reader_label})"), + ); spawn(async move { let choices = ["write a chunk".to_string(), "end the stream".to_string()]; loop { @@ -518,7 +523,12 @@ where let (writer, reader) = wit_future::new::< T, >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::dialog::print( + 0, + &format!("new (writer: {label}, reader: {reader_label})"), + ); spawn(async move { proxy::util::dialog::print(0, &format!("The host writes {label}")); let value: T = Dialog::read_value(1); diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 363d08f..951ac66 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -412,7 +412,11 @@ where { fn fuzz(u: &mut Unstructured<'_>) -> Result { let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::debug::print( + &format!("new (writer: {label}, reader: {reader_label})"), + ); let chunks: Vec> = Fuzz::fuzz(u)?; spawn(async move { for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { @@ -460,7 +464,11 @@ where let (writer, reader) = wit_future::new::< T, >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::debug::print( + &format!("new (writer: {label}, reader: {reader_label})"), + ); let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { if writer.write(value).await.is_ok() { diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 2d36377..6f6bbe4 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -401,7 +401,11 @@ where { fn fuzz(u: &mut Unstructured<'_>) -> Result { let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::debug::print( + &format!("new (writer: {label}, reader: {reader_label})"), + ); let chunks: Vec> = Fuzz::fuzz(u)?; spawn(async move { for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { @@ -449,7 +453,11 @@ where let (writer, reader) = wit_future::new::< T, >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); + let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); + proxy::util::debug::print( + &format!("new (writer: {label}, reader: {reader_label})"), + ); let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { if writer.write(value).await.is_ok() { From a3e19957fca60b8e0efd9ce963749e9d5d7306f1 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Sun, 1 Nov 2026 19:43:01 -0800 Subject: [PATCH 06/25] explain that the recorded handle id doesn't exactly match the main component --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 58d953e..31fab8b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,12 @@ Run `composed.wasm` in the host runtime which the original wasm is supposed to r The host runtime can also choose to implement the [`record` interface](https://github.com/chenyan2002/proxy-component/blob/main/assets/recorder.wit#L3). Then we can use the `--use-host-recorder` flag to skip composing the guest-side record implementation. +Note that the recorded handle id is not an exact match with the handle id the main component sees. Each +component instance has its own handle table, and the main component can allocate handles in it +that the proxy never sees, e.g., its own resources and streams. Each of those +shifts the main component's handle ids, so the imports component's handle ids, from its own table, +can't match them exactly. + ### Replay Assuming the trace captured from the record phase is stored in `trace.out`. We can run the following to replay the trace. From 981a22d6323385aa258b8dac77bf100a77caa2dd Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Mon, 2 Nov 2026 17:25:34 -0800 Subject: [PATCH 07/25] add design doc --- docs/p3-record-replay.md | 579 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 579 insertions(+) create mode 100644 docs/p3-record-replay.md diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md new file mode 100644 index 0000000..a4a42fe --- /dev/null +++ b/docs/p3-record-replay.md @@ -0,0 +1,579 @@ +# Record and replay of WASI p3 components + +proxy-component records and replays p2 components, and fuzzes and mocks (dialog mode) p3 +components. This file is the design for recording and replaying p3 components: the trace format, +how the proxy records a run without changing it, how replay reproduces it, and what makes replay +portable across runtimes. Nothing here is implemented yet, except a spike that tests the central +claim; see [Spike results](#spike-results) and [Phases](#phases). + +It builds on the cross-tool design in component-ui's +[`docs/p3.md`](https://github.com/chenyan2002/component-ui/blob/main/docs/p3.md), and was written +against wasmtime 49, wit-bindgen 0.63 (the `fix-async` fork) and proxy-component after fuzz and +dialog support for p3. + +## What exists + +- **Fuzz and dialog modes support p3.** The generated imports component mocks every import of the + main component, from its WIT, including async functions, streams, futures and error-contexts. + The generated `start` function is async and awaits async exports. +- **`proxy:conversion/executor`** ([`assets/executor.rs`](../assets/executor.rs)) runs the + virtualized host's end of streams and futures in the imports component. A sync mock, e.g. of + `consume-body`, can't block or spawn a task, so it queues the work, and the executor runs it in + the task of its `run` export, which `start` calls. The exports component's work is made by + `start` itself, so it spawns on the task of `start`. Fuzz mode waits for both at the end of + `start`; dialog mode drains them after each export call. +- **Labels.** Resources, streams, futures and error-contexts are labeled by handles in the + generated components' own tables, as [Labels](#labels) describes: a mock by the handle wasmtime + gives it, through an override of wit-bindgen's `_resource_new`, and a stream or future by the + end the proxy keeps. A stream or future the proxy creates is introduced with a + `new (writer: #stream-7, reader: #stream-5)` line, which goes away once fuzz and dialog label + streams as record does; see [Labels](#labels). + +Record and replay modes reject nothing up front, but they don't handle streams, futures or async +functions yet. + +## Where nondeterminism comes from + +Component model concurrency is cooperative: within one component instance one thread runs at a +time, and it observes other threads, or the host, only at defined points. Two kinds of choices +decide a run: + +- **The host's timing**: when an async import returns, whether it returns at once (RETURNED) or + starts a subtask (STARTED), when and how much the host reads from or writes to a stream or + future, and when the host calls an export. +- **The runtime's choices** at the points `Concurrency.md` (§Nondeterminism) lists: which pending + event `waitable-set.wait` or `poll` returns, which ready thread resumes, whether a wait with an + event already pending blocks, the order of tasks released from backpressure, and which thread + receives a cancellation. + +Calls between components, and streams and futures with wasm at both ends, add no choices of +their own. wasmtime 49 makes the runtime's choices without randomness: the lowest waitable first, +fixed run queues. jco makes them differently (join order, the oldest ready thread); see the table +in component-ui's `docs/p3.md`. + +## Trace format + +Backward compatibility with p2 traces is not a goal; the format is redesigned. A trace is the list +of lines the proxy observed, in the order it observed them, defined by the Rust types below, in +`crates/trace`. Replay relies on only part of that order; see Order, below. How a trace is stored is +up to the implementation. + +```rust +/// The lines the proxy observed, in the order it observed them. +pub struct Trace(pub Vec); + +/// One event, who did it, and, for a host line the guest observes outside its calls, the step in +/// which it happened; see [Steps](#steps). +pub struct Line { + pub by: Party, + pub at: Option, + pub event: Event, +} + +pub enum Party { + Host, + Guest, +} + +/// A run of the imports component's executor, or an export call's arrival, counted from 1; see +/// [Steps](#steps). +pub struct Step(pub u32); + +/// A call, numbered in order by the generated component that sees it, its `table`: the exports +/// component numbers the host's calls of exports, written `t1, t2, …`, and the imports component +/// the guest's calls of imports, written `c1, c2, …`. Sync calls are numbered too. +pub struct TaskId { + pub table: Table, + pub id: u32, +} + +/// A handle: the table its number is in, and its name as WAVE writes it, its type and its number, +/// e.g. `#stream-7` or `#fields-3`; see [Labels](#labels). +pub struct Label { + pub table: Table, + pub name: String, +} + +/// A generated component, which numbers handles and tasks: the imports component, whose handle +/// table holds the ends that cross at the main component's imports, and which numbers the guest's +/// calls of imports, and the exports component, for its exports. +pub enum Table { + Imports, + Exports, +} + +/// A value in WAVE, e.g. `ok(#response-3)`, whose type comes from where it appears: a function's +/// parameter or result, or a stream's or future's payload. +pub struct Wave(pub String); + +pub enum Event { + /// `by` is the caller: the host for an export, the guest for an import. + Call { id: TaskId, is_async: bool, func: String, args: Vec }, + /// The callee blocked before returning, so the caller got a subtask. Only for an async call. + Started { id: TaskId }, + /// The callee's result, `None` for a function without one. + Return { id: TaskId, value: Option }, + /// The caller asked to cancel a pending call. The callee answers with `Cancelled`, or with a + /// `Return`, if it returns anyway. + Cancel { id: TaskId }, + /// The callee acknowledged a cancellation instead of returning. + Cancelled { id: TaskId }, + /// A copy: the host wrote `items`, which the guest read. + Write { stream: Label, items: Wave }, + /// A copy: the host read `items`, which the guest wrote. + Read { stream: Label, items: Wave }, + /// A future's value was taken by its reader; `by` is the writer. + Value { future: Label, value: Wave }, + /// A stream or future end, or a resource, was dropped; `by` is who dropped it. + Drop { handle: Label }, +} +``` + +What the types don't enforce: + +- **Who acts.** `by` is the caller for `Call` and `Cancel`, the callee for `Started`, `Return` + and `Cancelled`, the host for `Write` and `Read`, the writer for `Value`, and whoever + dropped it for `Drop`. Most of it follows from the event; it is explicit, for readability. +- **Task ids.** A `TaskId` comes from a counter in the generated component that sees the call, + not from the runtime. wasmtime numbers its tasks internally, but no canonical built-in gives a + task's id to wasm, and other runtimes number tasks their own way. Each guest import call arrives + at the imports component as a new task, which nothing identifies but task-local storage + (`context.get`, `context.set`), which the callee fills itself. The subtask handles of the + imports component's own calls to the host are reused, and replay makes no host calls. Replay + numbers calls the same way, and they come in the same order, as replay checks each guest call. +- **Answers.** A sync call is answered by a `Return`; an async call by a `Return`, `Started` then + a `Return`, or `Started` then `Cancelled`, possibly with a `Cancel` in between. Other lines may + come between a call and its answer, e.g. host events of the other component, so the id pairs + them. A p2 component's calls are all sync, and each `Return` comes right after its `Call`. +- **Cancellation.** `Cancel` is a request: the caller's `subtask.cancel`, which the callee gets + as a cancellation request. The callee may acknowledge it, with `task.cancel`, which is + `Cancelled`, or return anyway, which is a `Return`, also when its result was already on its + way. A call that hasn't started yet is cancelled without the callee seeing it. It goes both + ways: the guest can cancel a pending import, and the host a pending export call. +- **Cancel and Drop** are about different things: `Cancel` about a pending call, `Drop` about a + handle. + - **`Cancel`.** In wit-bindgen, an async import call is a Rust `Future`; dropping it while the + call is pending cancels the call, with `[subtask-cancel]`: a `Cancel` of the task. + - **`Drop`** ends either end of a stream or future, or a resource. Dropping a writer ends the + stream, which the reader sees as its read returning `dropped`; dropping a reader, the writer + sees as its next write returning `dropped`. The proxy sees each side's drop that way, from its + own end, and records it as a `Drop` by that side. + - **A pending copy can be cancelled too**, with `stream.cancel-read` or `cancel-write`, which + doesn't end the stream. It isn't recorded: a cancelled copy never completes, so there is no + copy line, and the other end doesn't see it. +- **Labels** are handles in the two generated components' tables, and tasks are numbered by the + two components' counters, so the `table` of a label or a `TaskId` is part of what it names. A + handle's number is reused only after its handle has ended, which an earlier line records. A + label inside a `Wave` is written by its name alone; see [Labels](#labels) for its table. +- **Positions.** `at` is the step of a host line the guest observes outside its calls, including + an export call's arrival; other lines have none. See [Steps](#steps). +- **Copies.** A copy completes once, when both sides have acted, so it is one line. Copies are + lazy: if the guest never reads a stream the host writes, no `Write` appears. For `stream` a + line is the chunk one rendezvous copied, so replay can give the guest's reads the same chunks. + This holds for pumps that don't read ahead; [Read-ahead](#read-ahead-in-pumps) would change it. +- **Order.** The trace is not a total order. Each line belongs to a task, by its `TaskId`, or to a + handle, by its `Label`, and the lines of different tasks or handles may come in any order. What + orders them is each task's own sequence, `Call`, `Started`, `Return`; the `at` of the host lines + the guest observes outside its calls; and the ids, which are numbered in the order calls arrive. +- **No guest task on import calls.** The component model passes no caller identity, so the proxy + can't tell which of the guest's own tasks made an import call: `c5` can't be linked to the + export call `t1` that caused it. That limits deriving other orders from a trace: + - **What the trace does order:** a `Return` comes after its `Call` and `Started`, a copy after + its stream's first appearance, a `Cancelled` after its `Cancel`. + - **What it doesn't:** which guest lines a host line caused. If the guest's `Call c3` comes after + the host's `Return c2`, it may be the same guest task, reacting to `c2`, or an unrelated one. + Only in the second case may `Return c2` move after `Call c3`. + - **So** a reordered trace is checked by replaying it. If the guest diverges, the order wasn't + consistent, or the guest behaves differently in it; either way, the trace says nothing after + that point, which p3.md calls holes. Exact dependencies need caller identity, which the + [core-level shim](#portability) could provide, from the guest's task-local `context.get`. + +An example, for `GET /wait?ms=250` on [`examples/wasi-http-p3`](../examples/wasi-http-p3), writing a +`TaskId` as `t1` or `c1`, and a `Label` by its name; every label here is in the imports table: + +| `by` | `at` | `event` | +|---|---|---| +| host | 1 | `Call { id: t1, is_async: true, func: "wasi:http/handler@0.3.2.handle", args: ["#request-1"] }` | +| guest | | `Call { id: c1, is_async: false, func: "wasi:http/types/request@0.3.2.get-method", args: ["#borrow-request-1"] }` | +| host | | `Return { id: c1, value: Some("get") }` | +| guest | | `Call { id: c2, is_async: true, func: "wasi:clocks/monotonic-clock@0.3.2.wait-for", args: ["250000000"] }` | +| host | | `Started { id: c2 }` | +| host | 4 | `Return { id: c2, value: None }` | +| guest | | `Call { id: c3, is_async: false, func: "wasi:http/types/response@0.3.2.new", args: ["#fields-2", "some(#stream-5)", "#future-6"] }` | +| host | | `Return { id: c3, value: Some("(#response-3, #future-7)") }` | +| guest | | `Return { id: t1, value: Some("ok(#response-3)") }` | +| host | 6 | `Read { stream: #stream-5, items: "[97, 115, 107, …]" }` | +| guest | | `Drop { handle: #stream-5 }` | +| guest | | `Drop { handle: #future-7 }` | + +The lines about the response body, `#stream-5`, which the guest passed to `response.new`, come +after the export's `Return`: the host's `Read` of the body, and the guest's `Drop` of it, which ends +it. The guest returns the response before it writes the body, in a spawned task. The last line is +the guest dropping `#future-7`, which `response.new` returned. + +## Labels + +A label names a handle by its table and its name, as WAVE writes it: its type and the handle's +number in that table, e.g. `#fields-3` or `#stream-7`. + +- **Resources** are in the imports table, which holds them for both components. In record mode, + a host resource is labeled by the handle the imports component keeps for it, which the exports + component gets through `get-handle-*`; in fuzz and dialog modes, a mock by its handle, which + `_resource_new` gives it; in replay, a mock carries the trace's label. +- **Streams and futures** are labeled by the handle of the end the proxy received, which it keeps + for the stream's lifetime: the host's reader in an import's result or an export's arguments, the + guest's in an import's arguments or an export's result. The proxy pumps it through a pair of its + own, whose ends never appear in the trace. A stream or future first appears in the value that + brings it across, in the table of the component that call or result crosses, and every later + line uses the same label: + + | `by` | `at` | `event` | + |---|---|---| + | guest | | `Call { id: c4, is_async: false, func: "test:p3/host/body@0.1.0.consume", args: ["#body-1"] }` | + | host | | `Return { id: c4, value: Some("(#stream-6, #future-9)") }` | + | host | 5 | `Write { stream: #stream-6, items: "[52]" }` | + + - **Record mode** prints each value as it received it, before `to_proxy`. Today it prints + arguments after converting them, which has to change. + - **Fuzz and dialog** label as record does. A mock plays the host: it creates the host's pair + and keeps the writer, and the proxy keeps the reader, labels the stream by it, and pumps it to + the guest through a second pair. Today they hand that reader straight to the guest, which frees + its slot, so they label by the writer, and bind the two with a `new (writer: …, reader: …)` + line, which this format doesn't have. + - **Replay** creates a pair for each stream the host hands out, hands out its reader, and + performs the trace's lines on the stream's label through its writer. + - **The guest's `stream.new` and `future.new`** are built-ins inside its component that the + proxy doesn't see, so a stream the guest creates first appears where it crosses. Replay doesn't + need its creation, as the guest creates it again itself. +- **Two tables, neither the main component's.** The imports component keeps the ends that cross + at the main component's imports, the exports component those of its exports, and the two can + hold the same number. So a label carries its `table`. Neither table matches the main + component's handle ids, even in p2, as the main component's table also holds handles the proxy + never sees: its own resources and streams, waitable sets and subtasks. Labels only need to be + consistent: replay treats them as names, and a mock carries the trace's. + +Limits: + +- **An end that comes back, or passes through the guest, gets a new label.** When the guest hands + back a stream the proxy gave it, e.g. an `echo` that returns its argument, or passes a stream + from an export on to an import, the proxy gets a new handle, and nothing in the component model + tells it that this is the stream it already labeled. The trace stays consistent, and replays: a + reader of it just doesn't see the two labels as one stream. The [core-level shim](#portability) + could carry the label through, as the end keeps one handle in the guest's table from when it + arrives until the guest passes it on. +- **Replay can't check labels.** Replay's tables differ from the recording's, with mocks instead + of the host's ends and the pumps, so it can't recompute the labels, only use the trace's. +- `list` items are WAVE lists for now; p3.md's byte strings (`b"…"`) would be shorter. + +The alternative is to label handles by a counter, in the order they cross between the proxy and the +main component, shared by the two generated components through an interface of the imports +component. A number is then never reused, and the same in record and replay, so replay can check +each label, and there is no `table`. A resource can keep its label in its representation, but a +stream, future or error-context is a wit-bindgen type, which wasm-wave's `ToValue` prints by its +handle, so the proxy has to map each handle to its label, and set the lookup with wasm-wave's +`set_handle_label`. It has the same limit for ends that come back. + +## Recording + +Recording runs the composition in the real host, as for p2: the imports component forwards to +the host and records, and the exports component records the calls the host makes into the main +component. + +### The executor delivers and records host events + +In record mode, the imports component routes every interaction with the host through one +executor task, like fuzz mode's: + +- **Async imports.** The executor's job awaits the host's subtask, and completes the proxy's + export call with the result (its `task.return`). +- **Streams and futures, by pumps.** The proxy doesn't pass a stream end through, as the copies + would then go between the host and the guest without it. It keeps the end it received, creates + a pair of its own, and passes on that pair's other end. A pump is the executor job between the + two: it reads from one end, writes what it read to the other, and records each copy, and passes + on a drop the same way. A future is pumped once, for its value. + +So nothing the host does reaches the guest outside an executor run. That's why a step number can +mark when each host event happened. + +Putting the executor between the host and the guest must not change the run it records, which +these rules keep it from doing: + +- **Delivery on arrival.** The executor delivers each host result as soon as it arrives, without + holding or yielding, so that several results can be pending for the guest at once, as without + the proxy. +- **RETURNED is preserved.** When the host's result is ready on the first poll, the proxy's + export returns before it blocks, so the guest sees RETURNED; otherwise STARTED. Replay + reproduces it without a host: the mock of a call recorded RETURNED returns before it awaits, and + one recorded STARTED awaits its step first. This needs the host to be the runtime's own + functions, as it is under `wasmtime serve` or `run`; see [Spike results](#spike-results) for a + host that is a component. +- **No blocking WASI 0.2 calls during a run.** A sync WASI 0.2 function such as + `blocking-write-and-flush`, which `println!` calls, can suspend the instance in the middle of the + call, while it stays entered: other tasks run, and calls into the instance wait, which changes + the schedule, and which the proxy can't record. The proxy, and the bundled recorder, which + today prints each line as it is recorded, must buffer the trace, write it through a WASI 0.3 + `stream`, whose copies are async, or leave it to a host recorder. + +### Steps + +The imports component's executor is a single task that runs the proxy's jobs: the host's side of +its pending async imports, streams and futures (see [`assets/executor.rs`](../assets/executor.rs)). +It runs whenever the runtime polls its task: when it is started, and each time it is woken, by +new work being queued or by a host completion one of its jobs waits for. Each run is a **step**, +and so is each arrival of an export call, below. Steps are counted from 1, by one counter in the +imports component, and in each run the executor polls all its jobs. + +Replay has to know when to perform each host line. There are three kinds of lines: + +1. **Guest lines**, `by: guest`: the guest's calls, its export results, its drops. Replay doesn't + perform them: they happen when the guest does them, and replay checks each against the trace. + They have no `at`. +2. **Host lines that answer a guest call before it returns**: the `Return` of a sync call, or of a + RETURNED async one, and `Started`. The proxy did them while handling the call, before the call + returned to the guest, and replay does too: when the guest makes the call, replay performs them + at once, as part of answering it. The guest's `Call` line says when they happen, so they have no + `at`. +3. **Host lines the guest observes outside its calls**: what the host does on its own schedule, + rather than in answering a call. Mostly, the host returns something to the guest: the `Return` of + a STARTED call, the items of a `Write`, a future's `Value`. Also the host taking what the guest + wrote, a `Read`, which completes the guest's write; the host dropping its end, a `Drop`, which + the guest sees as `dropped`; a `Cancelled`; and the host calling an export, below. The executor + performs them when the host gets to them, so `at: n` says they happened in step `n`. In replay, + the executor counts its steps the same way, and a job holding such a line waits until the count + reaches `n`. There is no host to wake the executor, so a job waiting for a later step makes the + executor wake itself, by yielding, and its next run is the next step. + +**Export calls** arrive at the exports component, from the host, so their arrival is a step of its +own. On each arrival, the exports component calls `step` on `proxy:conversion/executor`, a sync +function that increments the counter and returns it, as the export `Call`'s `at`; the executor's +later runs are numbered after it. In replay, `start` makes an export call recorded at step `n` once +the count reaches `n - 1`, by calling `step` and then the export. An arrival can't fall within a +run of the executor: the host only calls an export while no wasm is running, unless something +suspends where the proxy can't see it, which the requirements below rule out. + +### Why the step is enough, and a gate isn't needed + +The trace order alone is not a replayable schedule. Say the guest has two `fetch` subtasks in one +waitable set, and is busy: + +1. The host finishes `b`, then `a`. The proxy records them in that order, and delivers both. +2. The guest then waits, finds both pending, and wasmtime returns the lowest waitable: `a`. +3. A replay that releases each event as soon as the trace cursor reaches it may release `b` while + the guest is waiting, so the guest takes `b` first: a different run. + +component-ui's `docs/p3.md` fixes this with a gate: hold every host result, and release one only +when the guest is idle. That excludes schedules: a guest never finds two results ready at once, +and every held call turns from RETURNED to STARTED. It also needs to know when the guest is idle, +which the proxy can't see. + +The step fixes it without holding anything. Every host event reaches the guest through the +executor, in one of its steps. If the runtime's choices are deterministic, everything that happens +between two steps follows from what happened before, so "host event H was performed in step n" is +a position that means the same in record and replay. Replay performs H in step n, +exactly where it was delivered, and the guest's waits fall in the same places. In the example, +both results are delivered before the guest waits, in record and in replay alike. + +From [the spike](#spike-results), with `fetch("b")`'s timer firing before `fetch("a")`'s. The +spike doesn't count its export call as a step: + +| `by` | `at` | `event` | | +|---|---|---|---| +| guest | | `Call { id: c1, …, func: "fetch", args: ["a"] }` | | +| host | | `Started { id: c1 }` | | +| guest | | `Call { id: c2, …, func: "fetch", args: ["b"] }` | | +| host | | `Started { id: c2 }` | steps 1 and 2: the executor's first run, and its run for the queued calls | +| host | 3 | `Return { id: c2, value: Some("B") }` | step 3: `b`'s completion woke the executor | +| host | 4 | `Return { id: c1, value: Some("A") }` | step 4: `a`'s completion woke it again | + +The guest returns `"BA"`. In replay there is no host: the mock of each call waits until the +executor's count reaches its `at`, and the executor, with a job waiting, yields, and comes back as +its next step. So `b`'s result is released in step 3 and `a`'s in step 4, and the guest's waits +receive them in the same order, with the same results in each. Released only by their order in the +trace, the two results could reach the guest before or after a different one of its waits. + +[The spike](#spike-results) confirmed this in wasmtime. It requires: + +- **Deterministic runtime choices**, see [Portability](#portability). +- **The same executor in record and replay**, with only the leaves different: host calls in + record, futures that the trace makes ready in replay. +- **Steps in replay where the host woke the executor in the recording.** Replay has no host, so a + job that waits for a later step makes the executor yield, and come back as the next step. + wasmtime runs the ready tasks first, as it did before the host's completion in the recording. +- **No suspension the proxy can't see**: no calls to other components where the recording calls + the host, and no blocking WASI 0.2 calls, as above. +- **One clock for both components.** The host calls exports through the exports component, not + the executor, so each arrival takes a step from the same counter, through `step` on + `proxy:conversion/executor`, as [Steps](#steps) describes. + +### What recording can't reach + +The proxy puts a hop, its own task, between the host and the guest. With delivery on arrival: + +| | | +|---|---| +| RETURNED | preserved, when the proxy forwards to host functions rather than another component | +| several host results pending at one guest wait | possible, as nothing holds them back. A wit-bindgen guest still takes them one per wakeup, the lowest waitable first, with or without the proxy: its callback receives one event at a time, and its futures don't see the others until their own events | +| a new export call | preserved: forwarded in the same task | +| a guest read or write that completes at once, as the host had data or was reading | only if the pump [reads ahead](#read-ahead-in-pumps); otherwise it blocks first | +| a host result arriving before the guest's next wait | may arrive after it instead, as the proxy's task needs a turn: a slower host, which is legal | + +Whatever schedule happens is recorded completely. Not every timing the unproxied component could +have is reachable while recording, as the proxy only makes the host look slower, or, with +read-ahead, eagerer. Covering other schedules is exploration, which fuzz mode does. + +### Read-ahead in pumps + +Whether a pump reads from one side before the other side asks is a choice of record mode's +pumps, and fuzz mode's, not of replay's. Replay has to make the same choice as the recording, as +it runs the same executor, and the guest sees the difference: whether its copy completes at once +or blocks first. + +- **Without read-ahead**, the pump reads from the host only once the guest reads, and reads from + the guest only once the host reads. Each of the guest's copies blocks first, so a guest copy + that completes at once is never recorded, nor is whatever the guest does differently after one. + The two copies of a chunk, between the host and the pump, and between the pump and the guest, + happen together, so one line describes both. +- **With read-ahead**, the pump keeps a read pending on the host's side, and data ready for the + guest, or the other way, for a stream the guest writes. Immediate copies become reachable, and + every copy the guest makes is still recorded. What changes is what the host sees: it is read + earlier, and possibly more, than the guest asked. + - Items the pump read ahead, but the guest never took, were written by the host, though no + guest read took them. + - If the guest drops its reader after `k` items, the host's next write returns `dropped` + without the proxy, but succeeds into the pump with it, and the host learns of the drop later. + - A host whose behavior depends on being read, e.g. a network body consumed earlier, or a + producer that produces only when read (wasmtime's `StreamProducer::poll_produce`), may then + behave in a way the unproxied composition wouldn't. + + Replay stays consistent, as it reproduces the recording, not the unproxied run; the recording + is skewed on the host's side. +- **Read-ahead changes what a copy line records.** The two copies of a chunk now happen at + different times. For the guest's read to complete at once in replay, replay's pump has to have + the data in the step record mode's pump got it from the host, so a `Write` is the host's chunk, + with the step it arrived in, and the guest's reads may split it, which replay derives again. A + `Read`, likewise, is the host's take from the pump, in the step it happened, which frees the + pump to take more from the guest; the guest's writes are checked as they happen. Copies are then + no longer lazy, and a line is no longer one rendezvous of the guest's. + +### Hosts + +- Recording a wasi-http component under `wasmtime serve -S p3,http`, or viceroy, needs no change. +- Recording under `proxy-component run` passes the main component's p3 imports through to the + host, so `run` needs `wasmtime_wasi::p3` and wasmtime-wasi-http's p3 linker, and, for + `wasi:http/handler`, a way to receive requests, as `--invoke` can't build one. + +## Spike results + +[`tests/spike-p3-race`](../tests/spike-p3-race) tests steps in wasmtime 49, with +hand-written components: a guest whose `run` calls `fetch("a")` and `fetch("b")` concurrently and +joins the results in the order its wakeups receive them; a record proxy whose `fetch` forwards to +wasmtime's `wait-for`, with delays from `DELAY_A` and `DELAY_B` or random, and whose executor +counts its steps; a replay proxy that delivers each result in its recorded step; and a driver +that runs the executor alongside the guest. `make -C tests/spike-p3-race` records each scenario, +replays it, and requires the same result, the same trace, and the same results in each of the +guest's wakeups. + +- **The clock works.** All scenarios replay exactly: each result first (`"AB"`, `"BA"`), each call + RETURNED, both RETURNED, the guest busy until both results are ready, and random delays. +- **RETURNED needs the host to be host functions.** With the host as a component instead + (`make -C tests/spike-p3-race component-host`), a call the proxy saw RETURNED reached the guest + as STARTED, and the guest got the two results in separate wakeups, while replay gave them + together. wasmtime runs the proxy's call into a component as a queued work item, which suspends + the proxy while it is entered, and holds back the guest's next call into it (`do_not_enter`, + `concurrent.rs:774`). Calling a + host component in replay as well made other scenarios diverge, as a component host suspends + differently in record and replay. Removing it, i.e. forwarding to host functions, fixed it. +- **Sync WASI 0.2 calls change the schedule.** At first the proxy and the guest printed each line + with `println!`, and several scenarios diverged; buffering the output fixed them. +- **Two results never reached the guest in one wakeup**, through the proxy, nor, in one-off + experiments outside the spike, through a component awaiting `wait-for` directly, or with the guest + awaiting two `wait-for`s itself, as a wit-bindgen guest takes one event per wakeup. wasmtime gave + the lowest waitable first: `a` first, even when `b`'s timer fired first. Only both RETURNED, + without events, gave both at once. +- **wit-bindgen moves a pending call between tasks.** The proxy's export polls the host call once, + for RETURNED, and hands it to the executor otherwise; the executor's poll moves its waitable to + the executor's task (wit-bindgen's `waitable.rs`, "cross-task situations"). + +## Replay + +Replay runs `composed.wasm`, with no host imports, as for p2. The imports component mocks every +import from the trace, the exports component's `start` makes the export calls, and the same +executor runs both. + +- **Host lines are performed** in their step `at`, or within the guest's call for those without one: + a `Return` or `Started` of an import, the chunk of a `Write`, the take of a `Read`, a host `Drop`, + the next export `Call`. An async mock whose call was RETURNED returns before it awaits, so the + guest sees RETURNED; a STARTED one returns after. +- **Guest lines are checked** when they happen: an import call with its arguments, an export's + result, the items of a copy from the guest, a guest drop. A mismatch is a divergence, reported + with the expected and the actual line. +- **Divergence** can also show up as a stall, when nothing can reach the next line; wasmtime then + traps with "deadlock detected", and the recorder prints the line it was waiting for. +- **Recorder interface.** `proxy:recorder` gets functions to record and read the new lines. + Waiting for a step is the executor's, not the recorder's. The bundled recorder + (`assets/recorder*.wasm`) and the host recorder of `run` (`--use-host-recorder`) implement them. + +## Portability + +**Replay in the runtime that recorded needs no change to anything.** It needs the runtime's +choices to be deterministic given the same inputs, the same runtime version and configuration, +and the same executor in record and replay. + +- **wasmtime 49** makes its choices without randomness. +- **jco** replays a composition with no async host imports with its fixed rules; its + `asyncDeterminism` setting is for this. Recording in jco meets its RETURNED race in + `_lowerImport`, which only changes what the proxy sees from the host, and the proxy records what + it saw. + +**Replay in another runtime** needs the runtime's choices to be the same in both. Two ways: + +1. **A deterministic scheduling profile** in the component model, as core Wasm 3.0 has for NaNs + and relaxed SIMD: fix each choice `Concurrency.md` lists, e.g. the lowest waitable first, the + oldest ready thread first, and when a host completion is delivered relative to ready wasm + tasks. A profile fixes the choices rather than recording them, so the composition stays as it + is. + - The process: an issue, then a PR to `WebAssembly/component-model`, updating + `design/mvp/Concurrency.md` and the reference `design/mvp/canonical-abi/definitions.py` + with tests, discussed in the WebAssembly CG's component model meetings; runtimes adopt it + behind a feature flag, as they do async (🚝) and error-context (📝). + - wasmtime and jco already have deterministic rules, but different ones, so the profile picks + one at each point. +2. **A shim inside the guest**, without anyone's agreement: rewrite the guest component to put a + core module between its core instance and its async built-ins (`waitable-set.wait`, `poll`, + `thread.yield`, the stream and future operations) and callback exports. The built-ins are + defined inside the guest component, and act on its own tables, so this can't be a separate + component; the shim reports each observation (the event a wait returned, a copy that blocked) + through a new import, e.g. `proxy:sched/hook`, composed with the proxy. Replay then releases + exactly what each recorded wait saw. The shim observes; replacing the built-ins would mean + reimplementing the component model's async runtime inside the guest. + +Patching the runtime, e.g. a scheduling hook in wasmtime, would also work, but replay must not +depend on runtime patches. wasmtime 49 has the start of its own record and replay +(`Config::rr`), without concurrency yet. + +## Phases + +1. **Spike, in wasmtime**: done, see [Spike results](#spike-results). +2. **Trace format** in `crates/trace`, and the recorder interface. +3. **Replay**, tested on traces that fuzz mode writes in this format, as its executor can count + steps too, and its mocks can label streams as record does. +4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. +5. **Portability across runtimes**: the profile proposal, or the shim. + +## Open questions + +1. **Steps with several arrivals**, and the host's side of stream copies (wasmtime's + `StreamProducer::poll_produce`, which is called when the guest reads): whether counting + steps places them exactly. +2. **[Read-ahead in pumps](#read-ahead-in-pumps)**: whether to read ahead, which makes the + guest's immediate copies reachable, at the cost of the host's view, and of copy lines that + record the host's chunks rather than the guest's. +3. **`list`** as WAVE lists or byte strings. +4. **Which way to portability**: the profile, the shim, or both. +5. **Hosts that are components**, e.g. virtualized WASI composed with the main component: whether + to support recording them, which needs the guest to always see STARTED, or the replay to + reproduce the component host's suspensions. +6. **Guests that see several events at once**: bindings that drain pending events, or call + `waitable-set.poll`, unlike wit-bindgen's; the spike covers only wit-bindgen guests. +7. **The recorder's output**: buffering it to the end, a WASI 0.3 stream, or only a host + recorder. From 4a51c513cf0b06d6c23c6310a1fa1de097c48b10 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Mon, 2 Nov 2026 19:03:22 -0800 Subject: [PATCH 08/25] fix --- docs/p3-record-replay.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index a4a42fe..cc394a8 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -415,12 +415,17 @@ The proxy puts a hop, its own task, between the host and the guest. With deliver | several host results pending at one guest wait | possible, as nothing holds them back. A wit-bindgen guest still takes them one per wakeup, the lowest waitable first, with or without the proxy: its callback receives one event at a time, and its futures don't see the others until their own events | | a new export call | preserved: forwarded in the same task | | a guest read or write that completes at once, as the host had data or was reading | only if the pump [reads ahead](#read-ahead-in-pumps); otherwise it blocks first | -| a host result arriving before the guest's next wait | may arrive after it instead, as the proxy's task needs a turn: a slower host, which is legal | +| a host result arriving before the guest's next wait | may arrive after it instead, as the executor needs a turn to pass it on: a slower host, which is legal. [Read-ahead](#read-ahead-in-pumps) doesn't help: an async import's result has nothing to read ahead, and a stream's data that arrives just before the wait still waits for the pump. A wit-bindgen guest, whose callback gets one event per wakeup, can't tell the difference when this is the only event, but can when another is pending at the same wait: without the proxy, the lowest waitable of the two comes first, with it, the other one. A guest that calls `waitable-set.poll` also finds nothing pending | Whatever schedule happens is recorded completely. Not every timing the unproxied component could have is reachable while recording, as the proxy only makes the host look slower, or, with read-ahead, eagerer. Covering other schedules is exploration, which fuzz mode does. +Only removing the hop would close the gap of a host result arriving late: the proxy would pass the +host's subtasks and stream ends straight through, and see nothing of them, so recording would have +to observe from inside the guest, as the [core-level shim](#portability) does, which sees what each +of the guest's waits returns. Replay would still mock the imports in the proxy. + ### Read-ahead in pumps Whether a pump reads from one side before the other side asks is a choice of record mode's From e6716fd2f1d036711d3e8c1606aeda4b095bc123 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Mon, 2 Nov 2026 19:19:16 -0800 Subject: [PATCH 09/25] correctness assumption --- docs/p3-record-replay.md | 67 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index cc394a8..7952cfc 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -518,6 +518,70 @@ executor runs both. Waiting for a step is the executor's, not the recorder's. The bundled recorder (`assets/recorder*.wasm`) and the host recorder of `run` (`--use-host-recorder`) implement them. +## Correctness + +Two questions, of which only the first can be guaranteed: + +1. **Soundness**: every run that recording sees replays exactly. +2. **Coverage**: recording sees every schedule the composition could have. It doesn't; see + [What recording can't reach](#what-recording-cant-reach). Replay only has to reproduce what was + recorded, and covering other schedules is exploration. + +### Soundness, by induction on steps + +A run is a function of its trace if the composition is deterministic apart from the inputs the +trace records: the classic condition for deterministic replay, that every nondeterministic choice +is recorded. Here it is an induction on steps: if record and replay are in the same state when +step `n` begins, and step `n` performs the same host lines, they are in the same state when step +`n + 1` begins. Between steps, the guest's lines are checked, and its calls answered by the lines +without an `at`. The induction holds under four assumptions, none of them proven yet: + +- **The executor is the only way in.** Every host event reaches the guest in a run of the + executor; see [Recording](#the-executor-delivers-and-records-host-events). +- **Between steps, everything is deterministic.** The runtime's choices are fixed, and nothing + suspends where the proxy can't see it: no host components, no blocking WASI 0.2 calls. The + component model doesn't guarantee this (`Concurrency.md` allows nondeterminism); wasmtime 49 + happens to, which is why replay in another runtime needs [the profile or the + shim](#portability). +- **The same executor in record and replay**, whose work in a step depends only on its state and + the trace's lines for that step. +- **The trace holds every input**: the values, RETURNED or STARTED, and the step of each host + line. + +### Fork-join program + +In a fork-join program without determinacy races, every schedule gives the same result (the +result behind Feng and Leiserson's SP-bags), so for such a guest, recording the values would be +enough, without steps. A component model async guest isn't one in general: it can observe the +order in which completions arrive, as the spike's guest does, joining results in the order its +wakeups receive them, and as a `select` does. That is a nondeterministic merge, outside fork-join's +determinacy, and it is what steps record. The theory tells which guests need steps, not that steps +are enough. + +For coverage, the theory to use is systematic concurrency testing: stateless model checking with +partial-order reduction (DPOR, as in CHESS), which, for a bounded program, visits every class of +schedules that differ only in the order of independent steps. The trace's partial order fits it, +but deciding which steps are independent needs caller identity on import calls, which only [the +shim](#portability) could provide; see No guest task on import calls, in +[Trace format](#trace-format). + +### Checking the implementation + +- **Replay checks itself, for each trace.** It checks every guest line, so a wrong replay shows up + as a divergence or a stall, not silently. It can miss only what the trace doesn't record, such + as guest-internal streams, or what the guest's waits returned, which the shim could record. +- **A fixpoint test**: record a run, replay it while recording, and require the two traces to be + equal. The spike does this, for its scenarios. +- **Enumerated scenarios**: for small guests, every placement of host completions across steps, + rather than random delays; guests that merge nondeterministically, which are the ones steps + exist for; and schedules that fuzz mode generates, recorded and replayed. +- **The assumptions, checked at run time**: in record mode, a host event that the guest observes + outside a step; in replay, a step count that differs from the recording's. +- **A model of the spec.** The canonical ABI's reference, `definitions.py`, is executable, and + marks its nondeterministic choices. Modeling the guest, the proxy and the scheduler there, and + checking exhaustively, for small programs, that replay at the recorded steps gives the same run, + would test the design independently of wasmtime: the closest to a proof, short of writing one. + ## Portability **Replay in the runtime that recorded needs no change to anything.** It needs the runtime's @@ -561,7 +625,8 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla 1. **Spike, in wasmtime**: done, see [Spike results](#spike-results). 2. **Trace format** in `crates/trace`, and the recorder interface. 3. **Replay**, tested on traces that fuzz mode writes in this format, as its executor can count - steps too, and its mocks can label streams as record does. + steps too, and its mocks can label streams as record does, with the checks of + [Correctness](#checking-the-implementation). 4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. 5. **Portability across runtimes**: the profile proposal, or the shim. From 9f9e476fbb1f967018380d44058622c72f6a2533 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Mon, 2 Nov 2026 22:01:49 -0800 Subject: [PATCH 10/25] cannot use epoch interruption --- docs/p3-record-replay.md | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 7952cfc..0945994 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -310,9 +310,28 @@ these rules keep it from doing: - **No blocking WASI 0.2 calls during a run.** A sync WASI 0.2 function such as `blocking-write-and-flush`, which `println!` calls, can suspend the instance in the middle of the call, while it stays entered: other tasks run, and calls into the instance wait, which changes - the schedule, and which the proxy can't record. The proxy, and the bundled recorder, which - today prints each line as it is recorded, must buffer the trace, write it through a WASI 0.3 - `stream`, whose copies are async, or leave it to a host recorder. + the schedule, and which the proxy can't record. So the proxy, the bundled recorder, which today + prints each line as it is recorded, and the debug component, whose prints fuzz mode and a + guest's debug builds use, must buffer their output and write it once the run is over, as the + spike does, or leave the trace to a host recorder. A buffer is lost if the run traps, and a run + that doesn't end, e.g. under `wasmtime serve`, has no end to write it at. +- **No epoch deadlines that yield.** wasmtime's epoch interruption ends a guest's time slice at a + point set by a timer thread, wherever the guest is. + - **A deadline that traps**, as `wasmtime -W timeout=…` sets, only ends the run, and the trace + with it. A run that finished before its deadline replays; one that hit it stops at a point + replay has no timer for, so replay goes on past the end of the trace. + - **A deadline that yields** (`epoch_deadline_async_yield_and_update`), as embedders use to + time-slice guests, suspends the guest in the middle of its computation, while its instance + stays entered. The executor can then run a step, and deliver a host event H, before the + guest's next wait W. Replay can't interrupt the guest, so it runs that step only once the + guest waits at W. Step numbers stay the same; what moves is where the step falls relative to + the guest's computation. The guest sees the difference when another event E is pending at W: + in record, H and E are both pending, and the runtime returns the lower waitable, say H; in + replay, only E is. + + A p2 guest isn't affected: it is sequential, and sees the host only through calls, so pausing it + changes nothing it can observe. [The shim](#portability) would lift this requirement, as it + positions host events by the guest's waits rather than by the executor's steps. ### Steps @@ -400,7 +419,7 @@ trace, the two results could reach the guest before or after a different one of job that waits for a later step makes the executor yield, and come back as the next step. wasmtime runs the ready tasks first, as it did before the host's completion in the recording. - **No suspension the proxy can't see**: no calls to other components where the recording calls - the host, and no blocking WASI 0.2 calls, as above. + the host, no blocking WASI 0.2 calls, and no epoch deadlines that yield, as above. - **One clock for both components.** The host calls exports through the exports component, not the executor, so each arrival takes a step from the same counter, through `step` on `proxy:conversion/executor`, as [Steps](#steps) describes. @@ -539,7 +558,8 @@ without an `at`. The induction holds under four assumptions, none of them proven - **The executor is the only way in.** Every host event reaches the guest in a run of the executor; see [Recording](#the-executor-delivers-and-records-host-events). - **Between steps, everything is deterministic.** The runtime's choices are fixed, and nothing - suspends where the proxy can't see it: no host components, no blocking WASI 0.2 calls. The + suspends where the proxy can't see it: no host components, no blocking WASI 0.2 calls, no epoch + deadlines that yield. The component model doesn't guarantee this (`Concurrency.md` allows nondeterminism); wasmtime 49 happens to, which is why replay in another runtime needs [the profile or the shim](#portability). @@ -614,7 +634,11 @@ and the same executor in record and replay. component; the shim reports each observation (the event a wait returned, a copy that blocked) through a new import, e.g. `proxy:sched/hook`, composed with the proxy. Replay then releases exactly what each recorded wait saw. The shim observes; replacing the built-ins would mean - reimplementing the component model's async runtime inside the guest. + reimplementing the component model's async runtime inside the guest. Positions by the guest's + waits, rather than by the executor's steps, also hold when the guest is suspended where the + proxy can't see it, e.g. by an epoch deadline that yields, or a blocking WASI 0.2 call: replay + releases each host event so that every wait finds the same events pending as in the + recording. Patching the runtime, e.g. a scheduling hook in wasmtime, would also work, but replay must not depend on runtime patches. wasmtime 49 has the start of its own record and replay @@ -645,5 +669,5 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla reproduce the component host's suspensions. 6. **Guests that see several events at once**: bindings that drain pending events, or call `waitable-set.poll`, unlike wit-bindgen's; the spike covers only wit-bindgen guests. -7. **The recorder's output**: buffering it to the end, a WASI 0.3 stream, or only a host - recorder. +7. **The recorder's output**: buffering it to the end, or only a host recorder; and where to + write a buffer when the run traps, or doesn't end, as under `wasmtime serve`. From 987e8279c7d65be52d6c20a1fe0da258dc05b659 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Tue, 3 Nov 2026 19:40:54 -0800 Subject: [PATCH 11/25] new trace format --- Cargo.lock | 3 +- Cargo.toml | 1 + Makefile | 2 +- assets/recorder.one_byte_page.wasm | Bin 242016 -> 303659 bytes assets/recorder.wasm | Bin 242020 -> 303663 bytes assets/recorder.wit | 65 ++- components/recorder/Cargo.toml | 1 - components/recorder/src/lib.rs | 75 ++- crates/trace/Cargo.toml | 2 + crates/trace/src/lib.rs | 496 +++++++++++++------ docs/p3-record-replay.md | 158 +++--- src/ast.rs | 4 + src/codegen/mod.rs | 52 ++ src/codegen/parse.rs | 5 + src/codegen/record.rs | 32 +- src/codegen/replay.rs | 50 +- src/run.rs | 72 +-- src/traits/proxy.rs | 13 +- tests/snapshots/record/record_exports/lib.rs | 152 ++++-- tests/snapshots/record/record_imports/lib.rs | 216 +++++--- tests/snapshots/replay/record_exports/lib.rs | 387 +++++++++------ tests/snapshots/replay/record_imports/lib.rs | 207 ++++++-- 22 files changed, 1315 insertions(+), 678 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4d5857..4ccec60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1386,6 +1386,7 @@ dependencies = [ "prettyplease 0.2.37", "proc-macro2", "quote", + "serde", "syn 2.0.119", "tempfile", "trace", @@ -1483,7 +1484,6 @@ name = "recorder" version = "0.1.0" dependencies = [ "libc-alloc", - "serde_json", "trace", "wit-bindgen 0.63.0", ] @@ -1876,6 +1876,7 @@ version = "0.1.0" dependencies = [ "serde", "serde_json", + "wit-bindgen 0.63.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0ffc27e..4a391e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ edition = "2024" [dependencies] trace = { path = "crates/trace" } +serde.workspace = true anyhow = "1.0.98" clap = { version = "4.5.40", features = ["derive"] } heck = "0.5.0" diff --git a/Makefile b/Makefile index 4e4b155..9aacdcd 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ test-fixture: wac compose $(FIXTURE)/compose.wac -d test:mockhost=$(FIXTURE_WASM)/proxy_host.wasm -d test:driver=$(FIXTURE_WASM)/proxy_driver.wasm -d test:main=composed.wasm -o $(FIXTURE_OUT)/record.wasm $(WASMTIME) --invoke 'run()' $(FIXTURE_OUT)/record.wasm > $(FIXTURE_OUT)/trace.out # the host's drops of the guest's sessions - grep -c '"ExportArgs":{"method":"drop"' $(FIXTURE_OUT)/trace.out | grep -qx 2 + grep -c '"by":"Host","at":null,"event":{"Drop"' $(FIXTURE_OUT)/trace.out | grep -qx 2 $(INSTRUMENT) -m replay $(FIXTURE_WASM)/proxy_guest.wasm $(WASMTIME) --invoke 'start()' composed.wasm < $(FIXTURE_OUT)/trace.out $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm diff --git a/assets/recorder.one_byte_page.wasm b/assets/recorder.one_byte_page.wasm index 046ae209a58528766f320cfff486a37310436429..e704d83ae88422036a5758141478cdbc3c6308d7 100644 GIT binary patch literal 303659 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 delta 77484 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 diff --git a/assets/recorder.wasm b/assets/recorder.wasm index 9e284ac7fe83c198c27efdaff75cacf0baabaf74..9c8ab956c7eecf28a560f9abe9a414f449928427 100644 GIT binary patch literal 303663 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 delta 77402 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 } + record return-value { id: task-id, value: option } + record chunk { %stream: label, items: string } + record future-value { %future: label, value: string } + + variant event { + // `by` is the caller. + call(call), + // The callee blocked before returning, so the caller got a subtask. + started(task-id), + %return(return-value), + cancel(task-id), + cancelled(task-id), + // The host wrote `items`, which the guest read. + write(chunk), + // The host read `items`, which the guest wrote. + read(chunk), + // A future's value was taken by its reader; `by` is the writer. + value(future-value), + // `by` is who dropped it. + drop(label), + } + + // `at` is the step of a host line the guest observes outside its calls. + record line { by: party, at: option, event: event } +} + interface %record { - record-args: func(method: option, args: list, is-export: bool); - record-ret: func(method: option, ret: option, is-export: bool); + use types.{line}; + // Records a line, in the order the proxy observes it. + %record: func(line: line); } interface replay { - replay-export: func() -> option>>; - assert-export-ret: func(method: option, ret: option); - replay-import: func(method: option, args: option>) -> option; + use types.{line}; + // The next line the host performs on its own: in trace order, the first host line that doesn't + // answer a guest call, e.g. an export call, or the host's drop of a guest resource. `none` at + // the end of the trace. Traps if a guest line the guest hasn't done comes before it. + next-host-line: func() -> option; + // Checks a guest line against the trace, and returns the host lines that answer it at once, + // e.g. the `return` of an import call. Traps on a mismatch, with the expected and the actual + // line. + check: func(line: line) -> list; } // Async, so that it can await the main component's async exports, and run the jobs of @@ -22,7 +70,12 @@ world host { import replay; } +// The types alone, which `crates/trace` generates its Rust types from. +world trace { + import types; +} + world guest { export %record; export replay; -} \ No newline at end of file +} diff --git a/components/recorder/Cargo.toml b/components/recorder/Cargo.toml index 2eb201e..fc0c50a 100644 --- a/components/recorder/Cargo.toml +++ b/components/recorder/Cargo.toml @@ -8,7 +8,6 @@ crate-type = ["cdylib"] [dependencies] trace = { path = "../../crates/trace" } -serde_json.workspace = true wit-bindgen.workspace = true libc-alloc = { path = "../../crates/libc-alloc", optional = true } diff --git a/components/recorder/src/lib.rs b/components/recorder/src/lib.rs index 7b57a36..ccf5db5 100644 --- a/components/recorder/src/lib.rs +++ b/components/recorder/src/lib.rs @@ -7,68 +7,57 @@ mod bindings { wit_bindgen::generate!({ path: "../../assets/recorder.wit", world: "guest", + // The trace's types, which `crates/trace` generates from the same WIT. + with: { "proxy:recorder/types@0.1.0": trace }, }); } -use trace::Logger; +use std::cell::RefCell; +use trace::{Line, Replay, Trace}; + struct Component; impl bindings::exports::proxy::recorder::record::Guest for Component { - fn record_args(method: Option, args: Vec, is_export: bool) { - let mut logger = Logger::new(); - let call = logger.record_args(method, args, is_export); - let json = serde_json::to_string(&call).unwrap(); - println!("{json}"); - } - fn record_ret(method: Option, ret: Option, is_export: bool) { - let mut logger = Logger::new(); - let call = logger.record_ret(method, ret, is_export); - let json = serde_json::to_string(&call).unwrap(); - println!("{json}"); + // Prints the stored form of the line, so that stdout is the trace. + fn record(line: Line) { + println!("{}", line.to_json()); } } -use std::cell::RefCell; thread_local! { - static TRACE: RefCell> = RefCell::new(None); + // The trace being replayed, read from stdin on first use. + static REPLAY: RefCell> = const { RefCell::new(None) }; } -fn load_trace() { - let load = TRACE.with_borrow(|v| v.is_none()); - if load { - TRACE.with_borrow_mut(|v| { +fn with_replay(f: impl FnOnce(&mut Replay) -> R) -> R { + REPLAY.with_borrow_mut(|replay| { + let replay = replay.get_or_insert_with(|| { use std::io::Read; let mut input = String::new(); std::io::stdin().read_to_string(&mut input).unwrap(); - let mut logger = Logger::new(); - logger.load_trace(&input); - *v = Some(logger); + Replay::new(Trace::parse(&input)) }); - } + f(replay) + }) } impl bindings::exports::proxy::recorder::replay::Guest for Component { - fn replay_export() -> Option<(String, Vec)> { - load_trace(); - TRACE.with_borrow_mut(|v| v.as_mut().unwrap().replay_export()) - } - fn assert_export_ret(assert_method: Option, assert_ret: Option) { - TRACE.with_borrow_mut(|v| { - v.as_mut() - .unwrap() - .assert_export_ret(assert_method, assert_ret) - }); + fn next_host_line() -> Option { + let line = with_replay(|replay| replay.next_host_line())?; + println!("{line}"); + Some(line) } - fn replay_import( - assert_method: Option, - assert_args: Option>, - ) -> Option { - TRACE.with_borrow_mut(|v| { - let (_, ret) = v - .as_mut() - .unwrap() - .replay_import(assert_method, assert_args, true); - ret - }) + fn check(line: Line) -> Vec { + println!("{line}"); + // A replayed `wasi:cli/exit` ends the replay with the guest's status. + if let Some(status) = line.exit_status() { + with_replay(|replay| replay.check(&line)); + std::process::exit(status); + } + let answers = with_replay(|replay| replay.check(&line)); + for answer in &answers { + println!("{answer}"); + } + answers } } bindings::export!(Component with_types_in bindings); diff --git a/crates/trace/Cargo.toml b/crates/trace/Cargo.toml index fb4614b..62cb5b9 100644 --- a/crates/trace/Cargo.toml +++ b/crates/trace/Cargo.toml @@ -6,3 +6,5 @@ edition = "2024" [dependencies] serde.workspace = true serde_json.workspace = true +# Generates the types from `assets/recorder.wit`. +wit-bindgen.workspace = true diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 96dcb57..4b50b17 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -1,173 +1,371 @@ -use std::collections::VecDeque; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum FuncCall { - ExportArgs { - method: String, - args: Vec, - }, - ExportRet { - method: Option, - ret: Option, - }, - ImportArgs { - method: Option, - args: Vec, - }, - ImportRet { - method: Option, - ret: Option, - }, +//! The trace of a run, which record mode writes and replay mode reads: the events the proxy +//! observed, as `docs/p3-record-replay.md` (Trace format) defines them. A trace is stored as one +//! JSON line per `Line`; other lines in the text, e.g. the guest's own output, are ignored. +//! +//! The types are generated from `proxy:recorder/types` (`assets/recorder.wit`), with serde, so +//! that the WIT defines them once: the bundled recorder maps the interface to this crate, with +//! wit-bindgen's `with` option, and a host converts its own bindings' types with `convert`, as +//! they serialize alike. Values are WAVE text, e.g. `ok(#response-3)`, whose type comes from +//! where they appear. +use std::fmt; + +use serde::{Deserialize, Serialize, de::DeserializeOwned}; + +mod bindings { + wit_bindgen::generate!({ + path: "../../assets/recorder.wit", + world: "trace", + additional_derives: [serde::Serialize, serde::Deserialize, PartialEq], + }); } +pub use bindings::proxy::recorder::types::*; -/// The method of a resource drop, whose only argument is the handle, e.g. `#x-1`. -pub const DROP: &str = "drop"; +/// The lines the proxy observed, in the order it observed them. +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +#[serde(transparent)] +pub struct Trace(pub Vec); -pub struct Logger(pub VecDeque); +/// Converts between this crate's types and another binding's types of `proxy:recorder/types`, +/// e.g. those of wasmtime's `bindgen!`, which derive serde with `additional_derives`. +pub fn convert(value: T) -> U { + serde_json::from_value(serde_json::to_value(value).unwrap()).unwrap() +} + +impl Event { + /// The task the event is about, for the events of a call. + pub fn task(&self) -> Option { + match self { + Event::Call(Call { id, .. }) + | Event::Started(id) + | Event::Return(ReturnValue { id, .. }) + | Event::Cancel(id) + | Event::Cancelled(id) => Some(*id), + _ => None, + } + } +} -impl Logger { - pub fn new() -> Self { - Self(VecDeque::new()) +impl Line { + /// Whether the line is the host's answer to a guest call, which the host gives while handling + /// the call: a `Started`, or the `Return` or `Cancelled` of a call that didn't start, of an + /// import. Replay performs it as part of answering the call, rather than on its own. + pub fn answers_guest(&self) -> bool { + self.by == Party::Host + && self.at.is_none() + && matches!( + self.event, + Event::Started(_) | Event::Return(_) | Event::Cancelled(_) + ) + && self.event.task().is_some_and(|id| id.table == Table::Imports) } - pub fn load_trace(&mut self, text: &str) { - self.0.clear(); - for line in text.lines() { - match serde_json::from_str::(line) { - Ok(item) => self.0.push_back(item), - // Ignore non-JSON lines. - Err(_) => continue, + /// The exit status of a call to `wasi:cli/exit`, which ends the run. + pub fn exit_status(&self) -> Option { + match &self.event { + Event::Call(Call { func, args, .. }) if func.starts_with("wasi:cli/exit") => { + let is_err = args.first().is_some_and(|arg| arg.starts_with("err")); + Some(if is_err { 1 } else { 0 }) } + _ => None, } } - pub fn dump_trace(&self) -> String { - self.0 - .iter() - .map(|call| serde_json::to_string(call).unwrap()) - .collect::>() - .join("\n") - } - pub fn record_args( - &mut self, - method: Option, - args: Vec, - is_export: bool, - ) -> FuncCall { - let call = if is_export { - FuncCall::ExportArgs { - method: method.unwrap(), - args, + /// The stored form of the line, one line of text. + pub fn to_json(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +impl Trace { + /// Reads a trace from its stored form, ignoring the lines that aren't trace lines, e.g. the + /// guest's output: those that aren't JSON objects with a `by` and an `event`. + /// + /// Panics on a trace line that doesn't parse, e.g. of another version of the format. + pub fn parse(text: &str) -> Self { + let lines = text.lines().filter_map(|text| { + let value: serde_json::Value = serde_json::from_str(text).ok()?; + let object = value.as_object()?; + if !object.contains_key("by") || !object.contains_key("event") { + return None; } - } else { - FuncCall::ImportArgs { method, args } - }; - self.0.push_back(call.clone()); - call - } - pub fn record_ret( - &mut self, - method: Option, - ret: Option, - is_export: bool, - ) -> FuncCall { - let call = if is_export { - FuncCall::ExportRet { method, ret } - } else { - FuncCall::ImportRet { method, ret } - }; - self.0.push_back(call.clone()); - call - } - pub fn replay_export(&mut self) -> Option<(String, Vec)> { - let call = self.0.pop_front()?; - println!("export call: {}", call.to_string()); - let FuncCall::ExportArgs { method, args } = call else { - panic!("the trace has {call:?}, which the guest didn't call") - }; - Some((method, args)) - } - pub fn assert_export_ret(&mut self, assert_method: Option, assert_ret: Option) { - if let Some(FuncCall::ExportRet { .. }) = self.0.front() { - let call = self.0.pop_front().unwrap(); - println!("export ret: {}", call.to_string()); - let FuncCall::ExportRet { method, ret } = call else { - panic!() - }; - if let (Some(method), Some(assert_method)) = (method, assert_method) { - assert_eq!(method, assert_method); + match serde_json::from_value(value) { + Ok(line) => Some(line), + Err(err) => panic!("invalid trace line `{text}`: {err}"), } - assert_eq!(ret, assert_ret); + }); + Trace(lines.collect()) + } +} + +impl fmt::Display for TaskId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let prefix = match self.table { + Table::Exports => 't', + Table::Imports => 'c', + }; + write!(f, "{prefix}{}", self.id) + } +} + +impl fmt::Display for Label { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.table { + Table::Imports => write!(f, "{}", self.name), + Table::Exports => write!(f, "{} (exports)", self.name), } } - pub fn replay_import( - &mut self, - assert_method: Option, - assert_args: Option>, - from_guest: bool, - ) -> (bool, Option) { - let mut exit_called = false; - let called = assert_method.clone(); - let mut call = self.0.pop_front().unwrap(); - if let FuncCall::ImportArgs { method, args } = &call { - if let (Some(method), Some(assert_method)) = (method, assert_method) { - assert_eq!(method, &assert_method); - } - if let Some(assert_args) = &assert_args { - assert_eq!(args, assert_args); +} + +impl fmt::Display for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Event::Call(Call { + id, + is_async, + func, + args, + }) => { + let is_async = if *is_async { "async " } else { "" }; + write!(f, "call {id} {is_async}{func}({})", args.join(", ")) } - println!("import call: {}", call.to_string()); - if method - .as_ref() - .is_some_and(|m| m.starts_with("wasi:cli/exit")) - { - if from_guest { - let code = if assert_args - .is_some_and(|args| args.get(0).is_some_and(|arg| arg.starts_with("err"))) - { - 1 - } else { - 0 - }; - std::process::exit(code); - } else { - exit_called = true; - return (exit_called, Some("Something that can crash".to_string())); - } + Event::Started(id) => write!(f, "started {id}"), + Event::Return(ReturnValue { id, value }) => { + write!(f, "return {id} {}", value.as_deref().unwrap_or("()")) } - call = self.0.pop_front().unwrap(); + Event::Cancel(id) => write!(f, "cancel {id}"), + Event::Cancelled(id) => write!(f, "cancelled {id}"), + Event::Write(Chunk { stream, items }) => write!(f, "write {stream} {items}"), + Event::Read(Chunk { stream, items }) => write!(f, "read {stream} {items}"), + Event::Value(FutureValue { future, value }) => write!(f, "value {future} {value}"), + Event::Drop(handle) => write!(f, "drop {handle}"), } - println!("import ret: {}", call.to_string()); - let FuncCall::ImportRet { ret, .. } = call else { - panic!( - "the guest called {}, but the trace has {call:?}", - called.as_deref().unwrap_or("an import"), - ) + } +} + +impl fmt::Display for Line { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let by = match self.by { + Party::Host => "host", + Party::Guest => "guest", }; - (exit_called, ret) + match self.at { + Some(at) => write!(f, "{by} @{at}: {}", self.event), + None => write!(f, "{by}: {}", self.event), + } } } -impl FuncCall { - pub fn to_string(&self) -> String { - match self { - // As component-ui writes drops, e.g. `drop #x-1` - FuncCall::ExportArgs { method, args } - | FuncCall::ImportArgs { - method: Some(method), - args, - } if method == DROP => { - format!("drop {}", args.join(", ")) +/// A trace being replayed: which of its lines replay has performed or checked. +pub struct Replay { + lines: Vec, + done: Vec, +} + +impl Replay { + pub fn new(trace: Trace) -> Self { + let done = vec![false; trace.0.len()]; + Replay { + lines: trace.0, + done, + } + } + fn pending(&self) -> impl Iterator { + self.lines + .iter() + .enumerate() + .filter(|(idx, _)| !self.done[*idx]) + } + /// The next line the host performs on its own: in trace order, the first host line that + /// doesn't answer a guest call, e.g. an export call, or the host's drop of a guest resource. + /// `None` at the end of the trace. + /// + /// Panics if a guest line comes before it, which the guest hasn't done. + pub fn next_host_line(&mut self) -> Option { + let (idx, line) = self + .pending() + .find(|(_, line)| line.by == Party::Guest || !line.answers_guest())?; + if line.by == Party::Guest { + panic!("the trace has `{line}`, which the guest didn't do"); + } + let line = line.clone(); + self.done[idx] = true; + Some(line) + } + /// Checks a guest line against the trace, and returns the host lines that answer it at once, + /// e.g. the `Return` of an import call. A guest line is matched by its task, for the lines of + /// a call, and otherwise as the trace's next guest line. + /// + /// Panics on a mismatch, with the expected and the actual line. + pub fn check(&mut self, actual: &Line) -> Vec { + assert_eq!(actual.by, Party::Guest, "`{actual}` isn't a guest line"); + let task = actual.event.task(); + let found = self.pending().find(|(_, line)| match task { + Some(task) => line.event.task() == Some(task), + None => line.by == Party::Guest, + }); + let Some((idx, expected)) = found else { + panic!("the guest did `{actual}`, which the trace doesn't have"); + }; + if expected.by != Party::Guest || expected.event != actual.event { + panic!("the trace has `{expected}`, but the guest did `{actual}`"); + } + self.done[idx] = true; + let Some(task) = task else { + return Vec::new(); + }; + // The task's next lines, while they answer the call. + let mut answers = Vec::new(); + for idx in idx + 1..self.lines.len() { + let line = &self.lines[idx]; + if self.done[idx] || line.event.task() != Some(task) { + continue; } - FuncCall::ExportArgs { method, args } => format!("{method}({})", args.join(", ")), - FuncCall::ImportArgs { method, args } => format!( - "{}({})", - method.as_deref().unwrap_or(""), - args.join(", ") - ), - FuncCall::ExportRet { ret, .. } | FuncCall::ImportRet { ret, .. } => { - ret.as_deref().unwrap_or("()").to_owned() + if !line.answers_guest() { + break; + } + self.done[idx] = true; + answers.push(line.clone()); + if !matches!(line.event, Event::Started(_)) { + break; } } + answers + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn task(table: Table, id: u32) -> TaskId { + TaskId { table, id } + } + fn line(by: Party, event: Event) -> Line { + Line { by, at: None, event } + } + fn call(id: TaskId, func: &str, args: &[&str]) -> Event { + Event::Call(Call { + id, + is_async: false, + func: func.to_string(), + args: args.iter().map(|arg| arg.to_string()).collect(), + }) + } + fn ret(id: TaskId, value: Option<&str>) -> Event { + Event::Return(ReturnValue { + id, + value: value.map(str::to_string), + }) + } + + /// An export call `t1`, during which the guest calls an import `c1`, then the host drops a + /// resource of the guest. + fn p2_trace() -> Trace { + let (t1, c1) = (task(Table::Exports, 1), task(Table::Imports, 1)); + Trace(vec![ + line(Party::Host, call(t1, "run", &["#request-1"])), + line(Party::Guest, call(c1, "get", &["1"])), + line(Party::Host, ret(c1, Some("\"a\""))), + line(Party::Guest, ret(t1, None)), + line( + Party::Host, + Event::Drop(Label { + table: Table::Exports, + name: "#session-1".to_string(), + }), + ), + ]) + } + + #[test] + fn stored_form() { + let trace = p2_trace(); + let text: Vec<_> = trace.0.iter().map(Line::to_json).collect(); + assert_eq!( + text[0], + r##"{"by":"Host","at":null,"event":{"Call":{"id":{"table":"Exports","id":1},"is_async":false,"func":"run","args":["#request-1"]}}}"## + ); + let text = format!("guest output\n{}\n", text.join("\n")); + assert_eq!(Trace::parse(&text), trace); + let step = Line { + at: Some(4), + ..trace.0[2].clone() + }; + assert_eq!(Trace::parse(&step.to_json()).0, vec![step]); + } + + #[test] + #[should_panic(expected = "invalid trace line")] + fn stored_form_mismatch() { + Trace::parse(r##"{"by":"Guest","at":null,"event":{"Drop":{"handle":"#x-1"}}}"##); + } + + #[test] + fn display() { + let lines: Vec<_> = p2_trace().0.iter().map(Line::to_string).collect(); + assert_eq!( + lines, + [ + "host: call t1 run(#request-1)", + "guest: call c1 get(1)", + "host: return c1 \"a\"", + "guest: return t1 ()", + "host: drop #session-1 (exports)", + ] + ); + } + + #[test] + fn replay() { + let trace = p2_trace(); + let mut replay = Replay::new(trace.clone()); + assert_eq!(replay.next_host_line(), Some(trace.0[0].clone())); + assert_eq!(replay.check(&trace.0[1]), vec![trace.0[2].clone()]); + assert_eq!(replay.check(&trace.0[3]), vec![]); + assert_eq!(replay.next_host_line(), Some(trace.0[4].clone())); + assert_eq!(replay.next_host_line(), None); + } + + #[test] + #[should_panic( + expected = "the trace has `guest: call c1 get(1)`, but the guest did `guest: call c1 get(2)`" + )] + fn replay_mismatch() { + let mut replay = Replay::new(p2_trace()); + replay.next_host_line(); + replay.check(&line( + Party::Guest, + call(task(Table::Imports, 1), "get", &["2"]), + )); + } + + #[test] + #[should_panic(expected = "the trace has `guest: call c1 get(1)`, which the guest didn't do")] + fn replay_missing_guest_line() { + let mut replay = Replay::new(p2_trace()); + replay.next_host_line(); + replay.next_host_line(); + } + + #[test] + fn started_then_return() { + let c1 = task(Table::Imports, 1); + let late = Line { + at: Some(3), + ..line(Party::Host, ret(c1, Some("5"))) + }; + let async_call = Event::Call(Call { + id: c1, + is_async: true, + func: "wait".to_string(), + args: vec![], + }); + let trace = Trace(vec![ + line(Party::Guest, async_call), + line(Party::Host, Event::Started(c1)), + late.clone(), + ]); + let mut replay = Replay::new(trace.clone()); + // The `Return` comes at its step, not in answer to the call. + assert_eq!(replay.check(&trace.0[0]), vec![trace.0[1].clone()]); + assert_eq!(replay.next_host_line(), Some(late)); } } diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 0945994..68ce916 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -29,8 +29,9 @@ dialog support for p3. `new (writer: #stream-7, reader: #stream-5)` line, which goes away once fuzz and dialog label streams as record does; see [Labels](#labels). -Record and replay modes reject nothing up front, but they don't handle streams, futures or async -functions yet. +- **Record and replay modes write and read this trace format** (`crates/trace`), through the + recorder interface (`assets/recorder.wit`), for p2 components. They reject nothing up front, + but they don't handle streams, futures or async functions yet. ## Where nondeterminism comes from @@ -54,79 +55,65 @@ in component-ui's `docs/p3.md`. ## Trace format Backward compatibility with p2 traces is not a goal; the format is redesigned. A trace is the list -of lines the proxy observed, in the order it observed them, defined by the Rust types below, in -`crates/trace`. Replay relies on only part of that order; see Order, below. How a trace is stored is -up to the implementation. - -```rust -/// The lines the proxy observed, in the order it observed them. -pub struct Trace(pub Vec); - -/// One event, who did it, and, for a host line the guest observes outside its calls, the step in -/// which it happened; see [Steps](#steps). -pub struct Line { - pub by: Party, - pub at: Option, - pub event: Event, +of lines the proxy observed, in the order it observed them. Replay relies on only part of that +order; see Order, below. How a trace is stored is up to the implementation. + +The format is defined once, as the WIT interface `proxy:recorder/types` +([`assets/recorder.wit`](../assets/recorder.wit)), which the generated components use to record +and check lines. `crates/trace` generates its Rust types from it, and the recorders use those: +the bundled recorder maps the interface to them, and the host recorder of `run` converts its own +bindings' types to them. Values are WAVE text, e.g. `ok(#response-3)`, whose type comes from +where they appear: a function's parameter or result, or a stream's or future's payload. + +```wit +enum party { host, guest } + +// A generated component, which numbers handles and tasks: the imports component, whose handle +// table holds the ends that cross at the main component's imports, and which numbers the guest's +// calls of imports, and the exports component, for its exports. +enum table { imports, exports } + +// A call, numbered in order by the generated component that sees it, its `table`: the exports +// component numbers the host's calls of exports, written `t1, t2, …`, and the imports component +// the guest's calls of imports, written `c1, c2, …`. Sync calls are numbered too. +record task-id { table: table, id: u32 } + +// A handle: the table its number is in, and its name as WAVE writes it, its type and its number, +// e.g. `#stream-7` or `#fields-3`; see Labels. +record label { table: table, name: string } + +record call { id: task-id, is-async: bool, %func: string, args: list } +// The callee's result, `none` for a function without one. +record return-value { id: task-id, value: option } +// The items one copy of a stream moved. +record chunk { %stream: label, items: string } +record future-value { %future: label, value: string } + +variant event { + // `by` is the caller: the host for an export, the guest for an import. + call(call), + // The callee blocked before returning, so the caller got a subtask. Only for an async call. + started(task-id), + %return(return-value), + // The caller asked to cancel a pending call. The callee answers with `cancelled`, or with a + // `return`, if it returns anyway. + cancel(task-id), + // The callee acknowledged a cancellation instead of returning. + cancelled(task-id), + // A copy: the host wrote the items, which the guest read. + write(chunk), + // A copy: the host read the items, which the guest wrote. + read(chunk), + // A future's value was taken by its reader; `by` is the writer. + value(future-value), + // A stream or future end, or a resource, was dropped; `by` is who dropped it. + drop(label), } -pub enum Party { - Host, - Guest, -} - -/// A run of the imports component's executor, or an export call's arrival, counted from 1; see -/// [Steps](#steps). -pub struct Step(pub u32); - -/// A call, numbered in order by the generated component that sees it, its `table`: the exports -/// component numbers the host's calls of exports, written `t1, t2, …`, and the imports component -/// the guest's calls of imports, written `c1, c2, …`. Sync calls are numbered too. -pub struct TaskId { - pub table: Table, - pub id: u32, -} - -/// A handle: the table its number is in, and its name as WAVE writes it, its type and its number, -/// e.g. `#stream-7` or `#fields-3`; see [Labels](#labels). -pub struct Label { - pub table: Table, - pub name: String, -} - -/// A generated component, which numbers handles and tasks: the imports component, whose handle -/// table holds the ends that cross at the main component's imports, and which numbers the guest's -/// calls of imports, and the exports component, for its exports. -pub enum Table { - Imports, - Exports, -} - -/// A value in WAVE, e.g. `ok(#response-3)`, whose type comes from where it appears: a function's -/// parameter or result, or a stream's or future's payload. -pub struct Wave(pub String); - -pub enum Event { - /// `by` is the caller: the host for an export, the guest for an import. - Call { id: TaskId, is_async: bool, func: String, args: Vec }, - /// The callee blocked before returning, so the caller got a subtask. Only for an async call. - Started { id: TaskId }, - /// The callee's result, `None` for a function without one. - Return { id: TaskId, value: Option }, - /// The caller asked to cancel a pending call. The callee answers with `Cancelled`, or with a - /// `Return`, if it returns anyway. - Cancel { id: TaskId }, - /// The callee acknowledged a cancellation instead of returning. - Cancelled { id: TaskId }, - /// A copy: the host wrote `items`, which the guest read. - Write { stream: Label, items: Wave }, - /// A copy: the host read `items`, which the guest wrote. - Read { stream: Label, items: Wave }, - /// A future's value was taken by its reader; `by` is the writer. - Value { future: Label, value: Wave }, - /// A stream or future end, or a resource, was dropped; `by` is who dropped it. - Drop { handle: Label }, -} +// One event, who did it, and, for a host line the guest observes outside its calls, the step in +// which it happened: a run of the imports component's executor, or an export call's arrival, +// counted from 1; see Steps. +record line { by: party, at: option, event: event } ``` What the types don't enforce: @@ -164,7 +151,7 @@ What the types don't enforce: - **Labels** are handles in the two generated components' tables, and tasks are numbered by the two components' counters, so the `table` of a label or a `TaskId` is part of what it names. A handle's number is reused only after its handle has ended, which an earlier line records. A - label inside a `Wave` is written by its name alone; see [Labels](#labels) for its table. + label inside a value is written by its name alone; see [Labels](#labels) for its table. - **Positions.** `at` is the step of a host line the guest observes outside its calls, including an export call's arrival; other lines have none. See [Steps](#steps). - **Copies.** A copy completes once, when both sides have acted, so it is one line. Copies are @@ -216,10 +203,12 @@ the guest dropping `#future-7`, which `response.new` returned. A label names a handle by its table and its name, as WAVE writes it: its type and the handle's number in that table, e.g. `#fields-3` or `#stream-7`. -- **Resources** are in the imports table, which holds them for both components. In record mode, - a host resource is labeled by the handle the imports component keeps for it, which the exports - component gets through `get-handle-*`; in fuzz and dialog modes, a mock by its handle, which - `_resource_new` gives it; in replay, a mock carries the trace's label. +- **Resources** of the host are in the imports table, which holds them for both components. In + record mode, a host resource is labeled by the handle the imports component keeps for it, which + the exports component gets through `get-handle-*`; in fuzz and dialog modes, a mock by its + handle, which `_resource_new` gives it; in replay, a mock carries the trace's label. A resource + of the guest, which the exports component wraps, is in the exports table, e.g. the host's drop + of a session, `drop #session-1 (exports)`. - **Streams and futures** are labeled by the handle of the end the proxy received, which it keeps for the stream's lifetime: the host's reader in an import's result or an export's arguments, the guest's in an import's arguments or an export's result. The proxy pumps it through a pair of its @@ -533,9 +522,13 @@ executor runs both. with the expected and the actual line. - **Divergence** can also show up as a stall, when nothing can reach the next line; wasmtime then traps with "deadlock detected", and the recorder prints the line it was waiting for. -- **Recorder interface.** `proxy:recorder` gets functions to record and read the new lines. - Waiting for a step is the executor's, not the recorder's. The bundled recorder - (`assets/recorder*.wasm`) and the host recorder of `run` (`--use-host-recorder`) implement them. +- **Recorder interface** (`assets/recorder.wit`): `types` mirrors the Rust types; `record` + records a line; `replay` has `next-host-line`, the next line the host performs on its own, e.g. + an export call, and `check`, which checks a guest line, and returns the host lines that answer + it at once, e.g. an import's `Return`. Waiting for a step is the executor's, not the recorder's; + phase 3 adds a way for the executor's jobs to take the host lines of their task or handle. The + bundled recorder (`assets/recorder*.wasm`) and the host recorder of `run` + (`--use-host-recorder`) implement it, with `Replay` of `crates/trace`. ## Correctness @@ -647,7 +640,8 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla ## Phases 1. **Spike, in wasmtime**: done, see [Spike results](#spike-results). -2. **Trace format** in `crates/trace`, and the recorder interface. +2. **Trace format** in `crates/trace`, and the recorder interface: done, for p2 record and + replay. 3. **Replay**, tested on traces that fuzz mode writes in this format, as its executor can count steps too, and its mocks can label streams as record does, with the checks of [Correctness](#checking-the-implementation). diff --git a/src/ast.rs b/src/ast.rs index 9b55771..bb38f3f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -323,6 +323,8 @@ impl<'a> Opt<'a> { LinkType::Debug } "proxy:util/dialog" => LinkType::Host, + // Only types, which no instance has to provide. + name if name.starts_with("proxy:recorder/types@") => LinkType::Host, name if name.starts_with("proxy:recorder/") => LinkType::Recorder, _ => LinkType::Host, }; @@ -339,6 +341,8 @@ impl<'a> Opt<'a> { } "proxy:conversion/conversion" | "proxy:conversion/executor" => LinkType::Imports, "proxy:util/dialog" => LinkType::Host, + // Only types, which no instance has to provide. + name if name.starts_with("proxy:recorder/types@") => LinkType::Host, name if name.starts_with("proxy:recorder/") => LinkType::Recorder, name if matches!(self.mode, Mode::Record) => match strip_wrapped(name) { Some(stripped) if self.main_exports.contains(stripped) => LinkType::Main, diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 9e358a5..dc3a6db 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -96,6 +96,9 @@ pub fn generate( state.generate_stubs(); let traits = crate::traits::TraitGenerator::new(&state).generate(); state.output.extend(traits); + if matches!(mode, Mode::Record | Mode::Replay) { + state.output.extend(trace_defs()); + } if matches!(mode, Mode::Replay) { let defs = state.replay_defs(); state.output.extend(defs); @@ -411,6 +414,55 @@ impl State { } } +/// Helpers of record and replay modes, which make the lines of `proxy:recorder/types`. Each +/// component numbers the calls it sees with its own counter: the exports component the host's +/// calls of exports, and the imports component the guest's calls of imports (see +/// `crates/trace`). +fn trace_defs() -> Vec { + let file: File = parse_quote! { + use bindings::proxy::recorder::types::*; + thread_local! { + // The number of the last call this component saw. + static TASK_ID: std::cell::Cell = const { std::cell::Cell::new(0) }; + } + #[allow(dead_code)] + fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID.with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } + } + #[allow(dead_code)] + fn trace_call(by: Party, id: TaskId, func: &str, is_async: bool, args: Vec) -> Line { + let call = Call { id, is_async, func: func.to_string(), args }; + Line { by, at: None, event: Event::Call(call) } + } + #[allow(dead_code)] + fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { by, at: None, event: Event::Return(ret) } + } + #[allow(dead_code)] + fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { by, at: None, event: Event::Drop(label) } + } + /// The value of the `return` among the host's answers to a call. + #[allow(dead_code)] + fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") + } + }; + file.items +} + /// `spawn` of the exports component, whose jobs, e.g. reading a stream an export returned, are /// only made by `start`, which is async: it spawns them on the task of `start`, which awaits them /// with `join_spawned`. The imports component's `spawn` queues them instead, as sync functions diff --git a/src/codegen/parse.rs b/src/codegen/parse.rs index b310d9f..ca0c631 100644 --- a/src/codegen/parse.rs +++ b/src/codegen/parse.rs @@ -8,6 +8,11 @@ use wit_parser::{TypeDefKind, TypeId}; impl State { pub fn find_all_items(&mut self, items: &[Item], current_path: Vec) { + // The recorder's imports, e.g. the lines of `proxy:recorder/types`, are the proxy's own, + // which the generated code calls, rather than interfaces of the main component. + if current_path.starts_with(&["proxy".to_string(), "recorder".to_string()]) { + return; + } for item in items { match item { // export functions diff --git a/src/codegen/record.rs b/src/codegen/record.rs index 8ec684b..4f7b3f1 100644 --- a/src/codegen/record.rs +++ b/src/codegen/record.rs @@ -53,45 +53,57 @@ impl State { quote! { mut } }; let display_name = &wit_func.display_name; - let is_export = !is_wrapped_module(&module_path[1]); + let is_async = wit_func.is_async; + // The host calls the exports, which the exports component numbers, and the guest the + // imports, which the imports component numbers. + let (table, caller, callee) = if is_wrapped_module(&module_path[1]) { + (quote! { Imports }, quote! { Guest }, quote! { Host }) + } else { + (quote! { Exports }, quote! { Host }, quote! { Guest }) + }; let record_ret = if get_return_type(&sig.output).is_none() { quote! { #func(#(#call_args),*); - proxy::recorder::record::record_ret(Some(#display_name), None, #is_export); + proxy::recorder::record::record(&trace_return(Party::#callee, __id, None)); } } else { quote! { let res = #func(#(#call_args),*); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret(Some(#display_name), Some(&wave_res), #is_export); + proxy::recorder::record::record(&trace_return(Party::#callee, __id, Some(wave_res))); #res } }; parse_quote! { #sig { + let __id = trace_task_id(Table::#table); let #is_mut params: Vec = #init_vec; #( let #arg_names = #arg_names.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* - proxy::recorder::record::record_args(Some(#display_name), ¶ms, #is_export); + proxy::recorder::record::record(&trace_call(Party::#caller, __id, #display_name, #is_async, params)); #record_ret } } } /// Overrides wit-bindgen's `resource_from_raw_`, which the `[dtor]` of an exported resource - /// calls, to record the drop of the resource in `module_path` around the actual drop. + /// calls, to record the drop of the resource in `module_path` before the actual drop: by the + /// guest, of a host resource the imports component wraps, or by the host, of a resource of + /// the guest, which the exports component wraps. pub fn generate_record_drop(&self, module_path: &[String]) -> syn::ImplItemFn { - let is_export = !is_wrapped_module(&module_path[1]); - let drop_name = trace::DROP; + let (by, table) = if is_wrapped_module(&module_path[1]) { + (quote! { Guest }, quote! { Imports }) + } else { + (quote! { Host }, quote! { Exports }) + }; parse_quote! { unsafe fn resource_from_raw_(handle: *mut Option) -> Option { // `None` if `into_inner` moved the resource out, e.g. to pass it on: not a drop. if let Some(res) = *unsafe { Box::from_raw(handle) } { - let params = vec![wasm_wave::to_string(&res.to_value()).unwrap()]; - proxy::recorder::record::record_args(Some(#drop_name), ¶ms, #is_export); + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + proxy::recorder::record::record(&trace_drop(Party::#by, Table::#table, name)); drop(res); - proxy::recorder::record::record_ret(Some(#drop_name), None, #is_export); } None } diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 76d693d..0c887a3 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -14,8 +14,15 @@ impl State { let wit_func = self.expect_wit_func(module_path, resource, func_name); let kind = wit_func.kind; let display_name = &wit_func.display_name; + let is_async = wit_func.is_async; let args = extract_arg_info(sig); let ret_ty = get_return_type(&sig.output); + // Checks the call, and takes the value of the `return` that answers it. + let check_call = quote! { + let __id = trace_task_id(Table::Imports); + let __call = trace_call(Party::Guest, __id, #display_name, #is_async, __params); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); + }; let replay_import = if let Some(ret_ty) = ret_ty { let to_rust = match wit_func.constructor_resource() { Some(name) => { @@ -24,13 +31,13 @@ impl State { None => quote! { ret.to_rust() }, }; quote! { - let wave = proxy::recorder::replay::replay_import(Some(#display_name), Some(&__params)).unwrap(); - let ret: Value = wasm_wave::from_str(&<#ret_ty as ValueTyped>::value_type(), &wave).unwrap(); + #check_call + let ret: Value = wasm_wave::from_str(&<#ret_ty as ValueTyped>::value_type(), &wave.unwrap()).unwrap(); #to_rust } } else { quote! { - let wave = proxy::recorder::replay::replay_import(Some(#display_name), Some(&__params)); + #check_call assert!(wave.is_none()); } }; @@ -62,17 +69,15 @@ impl State { } } /// Overrides wit-bindgen's `resource_from_raw_`, which the `[dtor]` of a mocked resource - /// calls, to check the drop with the trace. + /// calls, to check the guest's drop with the trace. pub fn generate_replay_drop(&self) -> syn::ImplItemFn { - let drop_name = trace::DROP; parse_quote! { unsafe fn resource_from_raw_(handle: *mut Option) -> Option { // `None` if `into_inner` moved the resource out: not a drop. if let Some(res) = *unsafe { Box::from_raw(handle) } { if !MOVING.with(|moving| moving.get()) { - let params = vec![wasm_wave::to_string(&res.to_value()).unwrap()]; - let ret = proxy::recorder::replay::replay_import(Some(#drop_name), Some(¶ms)); - assert!(ret.is_none()); + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + proxy::recorder::replay::check(&trace_drop(Party::Guest, Table::Imports, name)); } } None @@ -98,10 +103,9 @@ impl State { }; file.items } - /// The arm of the `start` function dropping a resource of the main component, which the - /// trace labels by its handle in this component, e.g. `#session-1`. + /// The arm of the `start` function for the host's drop of a resource of the main component, + /// which the trace labels by its handle in this component, e.g. `#session-1`. fn replay_export_drop(&self) -> Option { - let drop_name = trace::DROP; let resources: Vec<_> = self .types .iter() @@ -125,8 +129,9 @@ impl State { } }); Some(quote! { - #drop_name => { - let (name, handle) = args[0] + Event::Drop(label) => { + let (name, handle) = label + .name .strip_prefix('#') .and_then(|label| label.rsplit_once('-')) .and_then(|(name, handle)| Some((name, handle.parse::().ok()?))) @@ -135,7 +140,6 @@ impl State { #(#arms)* _ => unreachable!(), } - proxy::recorder::replay::assert_export_ret(Some(#drop_name), None); } }) } @@ -151,15 +155,16 @@ impl State { .. } = func; let arg_idx = (0..arg_names.len()).map(|idx| quote! { args[#idx] }); + // The export call's id is the trace's, as the trace makes the call. let assert_ret = if has_ret { quote! { let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret(Some(#display_name), Some(&wave_res)); + proxy::recorder::replay::check(&trace_return(Party::Guest, __id, Some(wave_res))); } } else { quote! { assert!(res == ()); - proxy::recorder::replay::assert_export_ret(Some(#display_name), None); + proxy::recorder::replay::check(&trace_return(Party::Guest, __id, None)); } }; // The host keeps the resources it gets, until the trace drops them. @@ -184,11 +189,16 @@ impl State { self.generate_start( sig, quote! { - while let Some((method, args)) = proxy::recorder::replay::replay_export() { - match method.as_str() { - #(#arms)* + while let Some(line) = proxy::recorder::replay::next_host_line() { + match line.event { + Event::Call(Call { id: __id, func, args, .. }) => { + match func.as_str() { + #(#arms)* + _ => unreachable!("the trace calls an unknown export {func}"), + } + } #drop_arm - _ => unreachable!(), + event => unreachable!("the host can't do {event:?} in a p2 trace"), } // clean up borrowed resources from input args SCOPED_ALLOC.with(|alloc| { diff --git a/src/run.rs b/src/run.rs index 934c0dd..3aa0f0f 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,7 +1,7 @@ use anyhow::Context; use clap::Parser; use std::path::PathBuf; -use trace::Logger; +use trace::{Replay, Trace}; use wasmtime::component::types::{ComponentExtern, ComponentFunc, ComponentItem as CItem}; use wasmtime::component::wasm_wave::{untyped::UntypedFuncCall, wasm::WasmFunc}; use wasmtime::component::{Component, HasSelf, Linker, ResourceTable, Val}; @@ -25,43 +25,58 @@ mod bindings { wasmtime::component::bindgen!({ path: "assets/recorder.wit", world: "host", + // To convert the lines to `crates/trace`'s types, which serialize alike (`trace::convert`). + additional_derives: [serde::Serialize, serde::Deserialize], }); } +use bindings::proxy::recorder::types::Line; + pub struct State { wasi_ctx: WasiCtx, resource_table: ResourceTable, - logger: Logger, + // The lines recorded, which `run` writes to `trace.out` at the end. + recorded: Vec, + replay: Option, exit_called: bool, } +impl bindings::proxy::recorder::types::Host for State {} impl bindings::proxy::recorder::record::Host for State { - fn record_args(&mut self, method: Option, args: Vec, is_export: bool) { - let call = self.logger.record_args(method, args, is_export); - println!("call: {}", call.to_string()); - } - fn record_ret(&mut self, method: Option, ret: Option, is_export: bool) { - let call = self.logger.record_ret(method, ret, is_export); - println!("ret: {}", call.to_string()); + fn record(&mut self, line: Line) { + let line: trace::Line = trace::convert(line); + println!("{line}"); + self.recorded.push(line); } } impl bindings::proxy::recorder::replay::Host for State { - fn replay_export(&mut self) -> Option<(String, Vec)> { - self.logger.replay_export() + fn next_host_line(&mut self) -> Option { + let line = self.replay.as_mut().unwrap().next_host_line()?; + println!("{line}"); + Some(trace::convert(line)) } - fn assert_export_ret(&mut self, assert_method: Option, assert_ret: Option) { - self.logger.assert_export_ret(assert_method, assert_ret); - } - fn replay_import( - &mut self, - assert_method: Option, - assert_args: Option>, - ) -> Option { - let (exit_called, ret) = self.logger.replay_import(assert_method, assert_args, false); - if exit_called { - self.exit_called = exit_called; - return Some("Something that can crash".to_string()); + fn check(&mut self, line: Line) -> Vec { + let line: trace::Line = trace::convert(line); + println!("{line}"); + let mut answers = self.replay.as_mut().unwrap().check(&line); + for answer in &answers { + println!("{answer}"); + } + // A replayed `wasi:cli/exit` traps the guest on purpose: the import has no result, so + // the mock traps on one. + if line.exit_status().is_some() { + self.exit_called = true; + let id = line.event.task().unwrap(); + let crash = trace::Event::Return(trace::ReturnValue { + id, + value: Some("Something that can crash".to_string()), + }); + answers = vec![trace::Line { + by: trace::Party::Host, + at: None, + event: crash, + }]; } - ret + trace::convert(answers) } } @@ -92,7 +107,8 @@ pub fn run(args: RunArgs) -> anyhow::Result<()> { let mut state = State { wasi_ctx: wasi, resource_table: ResourceTable::new(), - logger: Logger::new(), + recorded: Vec::new(), + replay: None, exit_called: false, }; dialog_bindings::proxy::util::dialog::add_to_linker::>( @@ -104,7 +120,7 @@ pub fn run(args: RunArgs) -> anyhow::Result<()> { state })?; let trace = std::fs::read_to_string(path)?; - state.logger.load_trace(&trace); + state.replay = Some(Replay::new(Trace::parse(&trace))); } else { bindings::proxy::recorder::record::add_to_linker::>( &mut linker, @@ -144,8 +160,8 @@ pub fn run(args: RunArgs) -> anyhow::Result<()> { } })?; if args.trace.is_none() { - let trace = store.data().logger.dump_trace(); - std::fs::write("trace.out", &trace)?; + let lines: Vec<_> = store.data().recorded.iter().map(trace::Line::to_json).collect(); + std::fs::write("trace.out", lines.join("\n"))?; } } let fuel = MAX_FUEL - store.get_fuel()?; diff --git a/src/traits/proxy.rs b/src/traits/proxy.rs index 59a0dfb..cf00b73 100644 --- a/src/traits/proxy.rs +++ b/src/traits/proxy.rs @@ -431,14 +431,13 @@ impl ProxyTrait<'_> { fn get_proxy_path(&self, src_path: &[String]) -> Vec { let from_export = src_path[0] == "exports"; let mut res = crate::codegen::get_proxy_path(src_path); - if from_export { - assert!(self.state.module_paths.contains(&res)); - } else if !self.state.module_paths.contains(&res) { + if !from_export && !self.state.module_paths.contains(&res) { res.remove(0); - assert!(self.state.module_paths.contains(&res)); - } else { - assert!(self.state.module_paths.contains(&res)); - }; + } + assert!( + self.state.module_paths.contains(&res), + "no module {res:?} for the types of {src_path:?}" + ); res } } diff --git a/tests/snapshots/record/record_exports/lib.rs b/tests/snapshots/record/record_exports/lib.rs index 9481d14..216f30f 100644 --- a/tests/snapshots/record/record_exports/lib.rs +++ b/tests/snapshots/record/record_exports/lib.rs @@ -8,109 +8,113 @@ impl exports::test::guest::api::Guest for Stub { p: exports::test::guest::api::Point, c: &exports::test::guest::api::Counter, ) -> Vec { + let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); let p = p.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::recorder::record::record_args( - Some("test:guest/api@0.1.0.run"), - ¶ms, - true, + proxy::recorder::record::record( + &trace_call(Party::Host, __id, "test:guest/api@0.1.0.run", false, params), ); let res = wrapped_test::guest::api::run(&p, &c); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:guest/api@0.1.0.run"), - Some(&wave_res), - true, + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, Some(wave_res)), ); res.to_proxy() } fn classify( s: exports::test::guest::api::Shape, ) -> ::core::option::Option { + let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); let s = s.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); - proxy::recorder::record::record_args( - Some("test:guest/api@0.1.0.classify"), - ¶ms, - true, + proxy::recorder::record::record( + &trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.classify", + false, + params, + ), ); let res = wrapped_test::guest::api::classify(&s); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:guest/api@0.1.0.classify"), - Some(&wave_res), - true, + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, Some(wave_res)), ); res.to_proxy() } fn begin( c: exports::test::guest::api::Counter, ) -> exports::test::guest::api::Session { + let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::recorder::record::record_args( - Some("test:guest/api@0.1.0.begin"), - ¶ms, - true, + proxy::recorder::record::record( + &trace_call(Party::Host, __id, "test:guest/api@0.1.0.begin", false, params), ); let res = wrapped_test::guest::api::begin(c); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:guest/api@0.1.0.begin"), - Some(&wave_res), - true, + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, Some(wave_res)), ); res.to_proxy() } } impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Session { fn new(name: String) -> Self { + let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); let name = name.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - proxy::recorder::record::record_args( - Some("new test:guest/api/session@0.1.0"), - ¶ms, - true, + proxy::recorder::record::record( + &trace_call( + Party::Host, + __id, + "new test:guest/api/session@0.1.0", + false, + params, + ), ); let res = Self::new(&name); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("new test:guest/api/session@0.1.0"), - Some(&wave_res), - true, + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, Some(wave_res)), ); res } fn name(&self) -> String { + let __id = trace_task_id(Table::Exports); let params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; - proxy::recorder::record::record_args( - Some("test:guest/api/session@0.1.0.name"), - ¶ms, - true, + proxy::recorder::record::record( + &trace_call( + Party::Host, + __id, + "test:guest/api/session@0.1.0.name", + false, + params, + ), ); let res = self.name(); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:guest/api/session@0.1.0.name"), - Some(&wave_res), - true, + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, Some(wave_res)), ); res.to_proxy() } unsafe fn resource_from_raw_(handle: *mut Option) -> Option { if let Some(res) = *unsafe { Box::from_raw(handle) } { - let params = vec![wasm_wave::to_string(& res.to_value()).unwrap()]; - proxy::recorder::record::record_args(Some("drop"), ¶ms, true); + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + proxy::recorder::record::record( + &trace_drop(Party::Host, Table::Exports, name), + ); drop(res); - proxy::recorder::record::record_ret(Some("drop"), None, true); } None } @@ -1257,3 +1261,65 @@ impl< Ok(x) } } +use bindings::proxy::recorder::types::*; +thread_local! { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} diff --git a/tests/snapshots/record/record_imports/lib.rs b/tests/snapshots/record/record_imports/lib.rs index bdcd559..3af812e 100644 --- a/tests/snapshots/record/record_imports/lib.rs +++ b/tests/snapshots/record/record_imports/lib.rs @@ -22,22 +22,25 @@ impl exports::proxy::conversion::conversion::Guest for Stub { impl exports::wrapped_test::host::types::Guest for Stub { type Counter = test::host::types::Counter; fn make_point(x: i32, y: i32) -> exports::wrapped_test::host::types::Point { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let x = x.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); let y = y.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); - proxy::recorder::record::record_args( - Some("test:host/types@0.1.0.make-point"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.make-point", + false, + params, + ), ); let res = test::host::types::make_point(x, y); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:host/types@0.1.0.make-point"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res.to_proxy() } @@ -45,168 +48,165 @@ impl exports::wrapped_test::host::types::Guest for Stub { p: exports::wrapped_test::host::types::Perms, c: exports::wrapped_test::host::types::Color, ) -> ::core::result::Result { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let p = p.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::recorder::record::record_args( - Some("test:host/types@0.1.0.check"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call(Party::Guest, __id, "test:host/types@0.1.0.check", false, params), ); let res = test::host::types::check(p, c); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:host/types@0.1.0.check"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res.to_proxy() } fn open( name: String, ) -> ::core::result::Result { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let name = name.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - proxy::recorder::record::record_args( - Some("test:host/types@0.1.0.open"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call(Party::Guest, __id, "test:host/types@0.1.0.open", false, params), ); let res = test::host::types::open(&name); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:host/types@0.1.0.open"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res.to_proxy() } } impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Counter { fn new(start: u32) -> Self { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let start = start.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); - proxy::recorder::record::record_args( - Some("new test:host/types/counter@0.1.0"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "new test:host/types/counter@0.1.0", + false, + params, + ), ); let res = Self::new(start); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("new test:host/types/counter@0.1.0"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res } fn get(&self) -> u32 { + let __id = trace_task_id(Table::Imports); let params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; - proxy::recorder::record::record_args( - Some("test:host/types/counter@0.1.0.get"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.get", + false, + params, + ), ); let res = self.get(); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:host/types/counter@0.1.0.get"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res.to_proxy() } fn add(&self, n: u32) -> () { + let __id = trace_task_id(Table::Imports); let mut params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; let n = n.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&n)).unwrap()); - proxy::recorder::record::record_args( - Some("test:host/types/counter@0.1.0.add"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.add", + false, + params, + ), ); self.add(n); - proxy::recorder::record::record_ret( - Some("test:host/types/counter@0.1.0.add"), - None, - false, - ); + proxy::recorder::record::record(&trace_return(Party::Host, __id, None)); } fn merge( a: exports::wrapped_test::host::types::CounterBorrow<'_>, b: exports::wrapped_test::host::types::Counter, ) -> exports::wrapped_test::host::types::Counter { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let a = a.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); let b = b.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); - proxy::recorder::record::record_args( - Some("test:host/types/counter@0.1.0.merge"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.merge", + false, + params, + ), ); let res = Self::merge(&a, b); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:host/types/counter@0.1.0.merge"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res.to_proxy() } unsafe fn resource_from_raw_(handle: *mut Option) -> Option { if let Some(res) = *unsafe { Box::from_raw(handle) } { - let params = vec![wasm_wave::to_string(& res.to_value()).unwrap()]; - proxy::recorder::record::record_args(Some("drop"), ¶ms, false); + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + proxy::recorder::record::record( + &trace_drop(Party::Guest, Table::Imports, name), + ); drop(res); - proxy::recorder::record::record_ret(Some("drop"), None, false); } None } } impl exports::wrapped_test::log0_1_0::sink::Guest for Stub { fn write(msg: String) -> () { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let msg = msg.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); - proxy::recorder::record::record_args( - Some("test:log/sink@0.1.0.write"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call(Party::Guest, __id, "test:log/sink@0.1.0.write", false, params), ); test::log0_1_0::sink::write(&msg); - proxy::recorder::record::record_ret( - Some("test:log/sink@0.1.0.write"), - None, - false, - ); + proxy::recorder::record::record(&trace_return(Party::Host, __id, None)); } } impl exports::wrapped_test::log0_2_0::sink::Guest for Stub { fn write(msg: String) -> bool { + let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); let msg = msg.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); - proxy::recorder::record::record_args( - Some("test:log/sink@0.2.0.write"), - ¶ms, - false, + proxy::recorder::record::record( + &trace_call(Party::Guest, __id, "test:log/sink@0.2.0.write", false, params), ); let res = test::log0_2_0::sink::write(&msg); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record_ret( - Some("test:log/sink@0.2.0.write"), - Some(&wave_res), - false, + proxy::recorder::record::record( + &trace_return(Party::Host, __id, Some(wave_res)), ); res.to_proxy() } @@ -1279,3 +1279,65 @@ impl< Ok(x) } } +use bindings::proxy::recorder::types::*; +thread_local! { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index 7c84bbe..f72ffce 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -5,165 +5,171 @@ bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); - while let Some((method, args)) = proxy::recorder::replay::replay_export() { - match method.as_str() { - "test:guest/api@0.1.0.run" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let p: test::guest::api::Point = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let c: test::guest::api::Counter = arg_value.to_rust(); - let res = test::guest::api::run(&p, &c); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:guest/api@0.1.0.run"), - Some(&wave_res), - ); - } - "test:guest/api@0.1.0.classify" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let s: test::guest::api::Shape = arg_value.to_rust(); - let res = test::guest::api::classify(&s); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:guest/api@0.1.0.classify"), - Some(&wave_res), - ); - } - "test:guest/api@0.1.0.begin" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let c: test::guest::api::Counter = arg_value.to_rust(); - let res = test::guest::api::begin(c); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:guest/api@0.1.0.begin"), - Some(&wave_res), - ); - std::mem::forget(res); - } - "new test:guest/api/session@0.1.0" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let name: String = arg_value.to_rust(); - let res = test::guest::api::Session::new(&name); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("new test:guest/api/session@0.1.0"), - Some(&wave_res), - ); - std::mem::forget(res); - } - "test:host/types@0.1.0.make-point" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let x: i32 = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let y: i32 = arg_value.to_rust(); - let res = test::host::types::make_point(x, y); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:host/types@0.1.0.make-point"), - Some(&wave_res), - ); - } - "test:host/types@0.1.0.check" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let p: test::host::types::Perms = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let c: test::host::types::Color = arg_value.to_rust(); - let res = test::host::types::check(p, c); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:host/types@0.1.0.check"), - Some(&wave_res), - ); - } - "test:host/types@0.1.0.open" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let name: String = arg_value.to_rust(); - let res = test::host::types::open(&name); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:host/types@0.1.0.open"), - Some(&wave_res), - ); - std::mem::forget(res); - } - "new test:host/types/counter@0.1.0" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let start: u32 = arg_value.to_rust(); - let res = test::host::types::Counter::new(start); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("new test:host/types/counter@0.1.0"), - Some(&wave_res), - ); - std::mem::forget(res); - } - "test:host/types/counter@0.1.0.merge" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let a: test::host::types::Counter = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let b: test::host::types::Counter = arg_value.to_rust(); - let res = test::host::types::Counter::merge(&a, b); - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::assert_export_ret( - Some("test:host/types/counter@0.1.0.merge"), - Some(&wave_res), - ); - std::mem::forget(res); + while let Some(line) = proxy::recorder::replay::next_host_line() { + match line.event { + Event::Call(Call { id: __id, func, args, .. }) => { + match func.as_str() { + "test:guest/api@0.1.0.run" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let p: test::guest::api::Point = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let c: test::guest::api::Counter = arg_value.to_rust(); + let res = test::guest::api::run(&p, &c); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + } + "test:guest/api@0.1.0.classify" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let s: test::guest::api::Shape = arg_value.to_rust(); + let res = test::guest::api::classify(&s); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + } + "test:guest/api@0.1.0.begin" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let c: test::guest::api::Counter = arg_value.to_rust(); + let res = test::guest::api::begin(c); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + std::mem::forget(res); + } + "new test:guest/api/session@0.1.0" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let name: String = arg_value.to_rust(); + let res = test::guest::api::Session::new(&name); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + std::mem::forget(res); + } + "test:host/types@0.1.0.make-point" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let x: i32 = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let y: i32 = arg_value.to_rust(); + let res = test::host::types::make_point(x, y); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + } + "test:host/types@0.1.0.check" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let p: test::host::types::Perms = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let c: test::host::types::Color = arg_value.to_rust(); + let res = test::host::types::check(p, c); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + } + "test:host/types@0.1.0.open" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let name: String = arg_value.to_rust(); + let res = test::host::types::open(&name); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + std::mem::forget(res); + } + "new test:host/types/counter@0.1.0" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let start: u32 = arg_value.to_rust(); + let res = test::host::types::Counter::new(start); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + std::mem::forget(res); + } + "test:host/types/counter@0.1.0.merge" => { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let a: test::host::types::Counter = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let b: test::host::types::Counter = arg_value.to_rust(); + let res = test::host::types::Counter::merge(&a, b); + let wave_res = wasm_wave::to_string(&res.to_value()) + .unwrap(); + proxy::recorder::replay::check( + &trace_return(Party::Guest, __id, Some(wave_res)), + ); + std::mem::forget(res); + } + _ => unreachable!("the trace calls an unknown export {func}"), + } } - "drop" => { - let (name, handle) = args[0] + Event::Drop(label) => { + let (name, handle) = label + .name .strip_prefix('#') .and_then(|label| label.rsplit_once('-')) .and_then(|(name, handle)| Some(( @@ -179,9 +185,8 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } _ => unreachable!(), } - proxy::recorder::replay::assert_export_ret(Some("drop"), None); } - _ => unreachable!(), + event => unreachable!("the host can't do {event:?} in a p2 trace"), } SCOPED_ALLOC .with(|alloc| { @@ -519,6 +524,68 @@ impl ToRust for Value { MockedResource { handle, name } } } +use bindings::proxy::recorder::types::*; +thread_local! { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index c170229..28a498d 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -26,14 +26,18 @@ impl exports::test::host::types::Guest for Stub { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("test:host/types@0.1.0.make-point"), - Some(&__params), - ) - .unwrap(); + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.make-point", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); let ret: Value = wasm_wave::from_str( &::value_type(), - &wave, + &wave.unwrap(), ) .unwrap(); ret.to_rust() @@ -45,17 +49,21 @@ impl exports::test::host::types::Guest for Stub { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("test:host/types@0.1.0.check"), - Some(&__params), - ) - .unwrap(); + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.check", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); let ret: Value = wasm_wave::from_str( &<::core::result::Result< exports::test::host::types::Shape, String, > as ValueTyped>::value_type(), - &wave, + &wave.unwrap(), ) .unwrap(); ret.to_rust() @@ -65,17 +73,21 @@ impl exports::test::host::types::Guest for Stub { ) -> ::core::result::Result { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("test:host/types@0.1.0.open"), - Some(&__params), - ) - .unwrap(); + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.open", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); let ret: Value = wasm_wave::from_str( &<::core::result::Result< exports::test::host::types::Counter, String, > as ValueTyped>::value_type(), - &wave, + &wave.unwrap(), ) .unwrap(); ret.to_rust() @@ -85,13 +97,20 @@ impl exports::test::host::types::GuestCounter for MockedResource { fn new(start: u32) -> Self { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("new test:host/types/counter@0.1.0"), - Some(&__params), + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "new test:host/types/counter@0.1.0", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); + let ret: Value = wasm_wave::from_str( + &::value_type(), + &wave.unwrap(), ) .unwrap(); - let ret: Value = wasm_wave::from_str(&::value_type(), &wave) - .unwrap(); MockedResource { name: "counter".to_string(), ..ret.to_rust() @@ -101,13 +120,20 @@ impl exports::test::host::types::GuestCounter for MockedResource { let mut __params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; - let wave = proxy::recorder::replay::replay_import( - Some("test:host/types/counter@0.1.0.get"), - Some(&__params), + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.get", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); + let ret: Value = wasm_wave::from_str( + &::value_type(), + &wave.unwrap(), ) .unwrap(); - let ret: Value = wasm_wave::from_str(&::value_type(), &wave) - .unwrap(); ret.to_rust() } fn add(&self, n: u32) -> () { @@ -115,10 +141,15 @@ impl exports::test::host::types::GuestCounter for MockedResource { wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; __params.push(wasm_wave::to_string(&ToValue::to_value(&n)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("test:host/types/counter@0.1.0.add"), - Some(&__params), + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.add", + false, + __params, ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); assert!(wave.is_none()); } fn merge( @@ -129,14 +160,18 @@ impl exports::test::host::types::GuestCounter for MockedResource { __params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); let __ret = { - let wave = proxy::recorder::replay::replay_import( - Some("test:host/types/counter@0.1.0.merge"), - Some(&__params), - ) - .unwrap(); + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.merge", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); let ret: Value = wasm_wave::from_str( &::value_type(), - &wave, + &wave.unwrap(), ) .unwrap(); ret.to_rust() @@ -149,12 +184,10 @@ impl exports::test::host::types::GuestCounter for MockedResource { unsafe fn resource_from_raw_(handle: *mut Option) -> Option { if let Some(res) = *unsafe { Box::from_raw(handle) } { if !MOVING.with(|moving| moving.get()) { - let params = vec![wasm_wave::to_string(& res.to_value()).unwrap()]; - let ret = proxy::recorder::replay::replay_import( - Some("drop"), - Some(¶ms), + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + proxy::recorder::replay::check( + &trace_drop(Party::Guest, Table::Imports, name), ); - assert!(ret.is_none()); } } None @@ -164,10 +197,15 @@ impl exports::test::log0_1_0::sink::Guest for Stub { fn write(msg: String) -> () { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("test:log/sink@0.1.0.write"), - Some(&__params), + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:log/sink@0.1.0.write", + false, + __params, ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); assert!(wave.is_none()); } } @@ -175,13 +213,20 @@ impl exports::test::log0_2_0::sink::Guest for Stub { fn write(msg: String) -> bool { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); - let wave = proxy::recorder::replay::replay_import( - Some("test:log/sink@0.2.0.write"), - Some(&__params), + let __id = trace_task_id(Table::Imports); + let __call = trace_call( + Party::Guest, + __id, + "test:log/sink@0.2.0.write", + false, + __params, + ); + let wave = trace_return_value(proxy::recorder::replay::check(&__call)); + let ret: Value = wasm_wave::from_str( + &::value_type(), + &wave.unwrap(), ) .unwrap(); - let ret: Value = wasm_wave::from_str(&::value_type(), &wave) - .unwrap(); ret.to_rust() } } @@ -449,6 +494,68 @@ impl ToRust for Value { MockedResource { handle, name } } } +use bindings::proxy::recorder::types::*; +thread_local! { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; From d3874d0d361d9141493ba3705c8f7f7a2ad4374d Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Wed, 4 Nov 2026 15:56:23 -0800 Subject: [PATCH 12/25] make fuzz to use the new trace --- README.md | 2 +- assets/executor.rs | 64 ++ assets/executor.wit | 7 + docs/p3-record-replay.md | 99 ++- src/ast.rs | 6 +- src/codegen/dialog.rs | 27 +- src/codegen/fuzz.rs | 155 +++-- src/codegen/mock.rs | 93 +-- src/codegen/mod.rs | 139 ++++- src/codegen/replay.rs | 26 +- src/codegen/wit.rs | 11 +- src/instrument.rs | 6 +- src/traits/dialog.rs | 32 +- src/traits/fuzz.rs | 153 +++-- src/traits/mod.rs | 70 +++ tests/snapshots/dialog/record_exports/lib.rs | 11 +- tests/snapshots/dialog/record_imports/lib.rs | 111 +++- tests/snapshots/fuzz/record_exports/lib.rs | 412 +++++++++++-- tests/snapshots/fuzz/record_imports/lib.rs | 445 ++++++++++++-- tests/snapshots/fuzz/wit/component.wit | 2 + .../snapshots/p3-dialog/record_exports/lib.rs | 83 ++- .../snapshots/p3-dialog/record_imports/lib.rs | 183 ++++-- tests/snapshots/p3-fuzz/record_exports/lib.rs | 545 ++++++++++++++--- tests/snapshots/p3-fuzz/record_imports/lib.rs | 574 +++++++++++++++--- tests/snapshots/p3-fuzz/wit/component.wit | 2 + tests/snapshots/replay/record_exports/lib.rs | 6 + tests/snapshots/replay/record_imports/lib.rs | 71 ++- 27 files changed, 2660 insertions(+), 675 deletions(-) diff --git a/README.md b/README.md index 31fab8b..440b770 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ $ proxy-component instrument -m fuzz $ wasmtime --invoke 'start()' composed.wasm ``` -Fuzzing the import return and export input based on the WIT type. This mode requires a [Debug component](components/debug/) to get random numbers and logging. The `composed.wasm` can be run in +Fuzzing the import return and export input based on the WIT type. This mode requires a [Debug component](components/debug/) to get random numbers. The run prints its trace to stdout, in the same format as record mode, so `wasmtime --invoke 'start()' composed.wasm > trace.out` stores it. The `composed.wasm` can be run in a standalone `wasmtime` without any special host functions. ### Dialog diff --git a/assets/executor.rs b/assets/executor.rs index ce81095..fbe322a 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -42,6 +42,12 @@ // // The flip side: if the guest never finishes, or drops, a stream or future it gave the host, its // job never ends, so neither do `run` nor `start`, and wasmtime traps with "deadlock detected". +// +// Steps: each run of the executor, i.e. each poll of `run_executor`, is a step, and so is each +// arrival of an export call, which the exports component counts with the `step` export. Host +// lines delivered outside a call carry the step, `at`, in which they happened (see +// `docs/p3-record-replay.md`, Steps). A job that waits for a later run, e.g. an async import +// the fuzzer made STARTED, makes the executor wake itself, as a host completion would. type Job = std::pin::Pin>>; #[derive(Default)] struct Executor { @@ -51,6 +57,58 @@ struct Executor { } thread_local! { static EXECUTOR: std::cell::RefCell = std::cell::RefCell::new(Executor::default()); + // The last step, counted from 1. + static STEP: std::cell::Cell = const { std::cell::Cell::new(0) }; + // Set by a job waiting for a later run, so that the executor wakes itself. + static WAKE_AGAIN: std::cell::Cell = const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. A job counts them, waking the executor itself in between. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + #[derive(Default)] + struct Done { + step: Option, + waker: Option, + } + let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_done = done.clone(); + let mut left = runs; + spawn(std::future::poll_fn(move |_| { + if left == 0 { + let mut done = job_done.borrow_mut(); + done.step = Some(current_step()); + if let Some(waker) = done.waker.take() { + waker.wake(); + } + return std::task::Poll::Ready(()); + } + left -= 1; + WAKE_AGAIN.set(true); + std::task::Poll::Pending + })); + std::future::poll_fn(move |cx| { + let mut done = done.borrow_mut(); + match done.step { + Some(step) => std::task::Poll::Ready(step), + None => { + done.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -68,6 +126,8 @@ fn spawn(job: impl std::future::Future + 'static) { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + next_step(); + WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); // A job may spawn another, so `EXECUTOR` isn't borrowed while jobs run. loop { @@ -83,6 +143,10 @@ async fn run_executor() { std::task::Poll::Ready(()) } else { e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + // Yields, and comes back as the next run. + cx.waker().wake_by_ref(); + } std::task::Poll::Pending } }) diff --git a/assets/executor.wit b/assets/executor.wit index 101a241..7848ac5 100644 --- a/assets/executor.wit +++ b/assets/executor.wit @@ -8,4 +8,11 @@ package proxy:conversion; interface executor { run: async func(); stop: func(); + // Counts a step that isn't a run of the executor, e.g. the arrival of an export call, which the + // exports component makes, and returns it (see `docs/p3-record-replay.md`, Steps). + step: func() -> u32; + // Whether the exports component is dropping mocks of the imports component itself, e.g. those + // it made for the borrowed arguments of an export call: the host's drops, not the guest's, + // which take a step, as an export call does. + host-drops: func(dropping: bool); } diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 68ce916..7b43695 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -3,8 +3,7 @@ proxy-component records and replays p2 components, and fuzzes and mocks (dialog mode) p3 components. This file is the design for recording and replaying p3 components: the trace format, how the proxy records a run without changing it, how replay reproduces it, and what makes replay -portable across runtimes. Nothing here is implemented yet, except a spike that tests the central -claim; see [Spike results](#spike-results) and [Phases](#phases). +portable across runtimes. What is implemented so far is listed below, and in [Phases](#phases). It builds on the cross-tool design in component-ui's [`docs/p3.md`](https://github.com/chenyan2002/component-ui/blob/main/docs/p3.md), and was written @@ -22,16 +21,10 @@ dialog support for p3. the task of its `run` export, which `start` calls. The exports component's work is made by `start` itself, so it spawns on the task of `start`. Fuzz mode waits for both at the end of `start`; dialog mode drains them after each export call. -- **Labels.** Resources, streams, futures and error-contexts are labeled by handles in the - generated components' own tables, as [Labels](#labels) describes: a mock by the handle wasmtime - gives it, through an override of wit-bindgen's `_resource_new`, and a stream or future by the - end the proxy keeps. A stream or future the proxy creates is introduced with a - `new (writer: #stream-7, reader: #stream-5)` line, which goes away once fuzz and dialog label - streams as record does; see [Labels](#labels). - -- **Record and replay modes write and read this trace format** (`crates/trace`), through the - recorder interface (`assets/recorder.wit`), for p2 components. They reject nothing up front, - but they don't handle streams, futures or async functions yet. +- **The trace format** ([Trace format](#trace-format)) and its recorder interface. Fuzz mode + writes it for p2 and p3 components, with [steps](#steps) and [labels](#labels). Record and + replay modes write and read it for p2 components; they reject nothing up front, but don't handle + streams, futures or async functions yet. ## Where nondeterminism comes from @@ -205,10 +198,13 @@ number in that table, e.g. `#fields-3` or `#stream-7`. - **Resources** of the host are in the imports table, which holds them for both components. In record mode, a host resource is labeled by the handle the imports component keeps for it, which - the exports component gets through `get-handle-*`; in fuzz and dialog modes, a mock by its - handle, which `_resource_new` gives it; in replay, a mock carries the trace's label. A resource - of the guest, which the exports component wraps, is in the exports table, e.g. the host's drop - of a session, `drop #session-1 (exports)`. + the exports component gets through `get-handle-*`; in fuzz and dialog modes, a mock by a number + the imports component gives it when it makes it, counting from 1, which no other mock gets; in + replay, a mock carries the trace's label. The mock's own handle can't label it: it moves on with + the mock, which frees its number, and a borrow the guest passes back comes in as the mock + itself, without a handle. Fuzz mode records the end of every mock, also the host's drops, which + record mode can't see. A resource of the guest, which the exports component wraps, is in the + exports table, e.g. the host's drop of a session, `drop #session-1 (exports)`. - **Streams and futures** are labeled by the handle of the end the proxy received, which it keeps for the stream's lifetime: the host's reader in an import's result or an export's arguments, the guest's in an import's arguments or an export's result. The proxy pumps it through a pair of its @@ -224,11 +220,13 @@ number in that table, e.g. `#fields-3` or `#stream-7`. - **Record mode** prints each value as it received it, before `to_proxy`. Today it prints arguments after converting them, which has to change. - - **Fuzz and dialog** label as record does. A mock plays the host: it creates the host's pair - and keeps the writer, and the proxy keeps the reader, labels the stream by it, and pumps it to - the guest through a second pair. Today they hand that reader straight to the guest, which frees - its slot, so they label by the writer, and bind the two with a `new (writer: …, reader: …)` - line, which this format doesn't have. + - **Fuzz and dialog modes** make the stream themselves: they keep the writer, and hand the + reader out, which frees the reader's slot. So they label the stream by the writer, also where + the reader is printed, in the value that hands it out: they register the pair with wasm-wave's + `set_handle_label`, which prints the reader under its writer's number until the reader has + been printed. Pumping it through a pair the mock keeps the reader of, as record mode does, + isn't possible: wasmtime traps a copy between two ends of one component instance, unless + the payload is numeric. - **Replay** creates a pair for each stream the host hands out, hands out its reader, and performs the trace's lines on the stream's label through its writer. - **The guest's `stream.new` and `future.new`** are built-ins inside its component that the @@ -254,13 +252,10 @@ Limits: of the host's ends and the pumps, so it can't recompute the labels, only use the trace's. - `list` items are WAVE lists for now; p3.md's byte strings (`b"…"`) would be shorter. -The alternative is to label handles by a counter, in the order they cross between the proxy and the -main component, shared by the two generated components through an interface of the imports -component. A number is then never reused, and the same in record and replay, so replay can check -each label, and there is no `table`. A resource can keep its label in its representation, but a -stream, future or error-context is a wit-bindgen type, which wasm-wave's `ToValue` prints by its -handle, so the proxy has to map each handle to its label, and set the lookup with wasm-wave's -`set_handle_label`. It has the same limit for ends that come back. +Mocks already use a counter. Extending it to every handle, shared by the two generated +components, would make a number never reused, and the same in record and replay, so that replay +could check each label, without a `table`; streams, futures and error-contexts would map each +handle to its number with `set_handle_label`. It has the same limit for ends that come back. ## Recording @@ -319,8 +314,7 @@ these rules keep it from doing: replay, only E is. A p2 guest isn't affected: it is sequential, and sees the host only through calls, so pausing it - changes nothing it can observe. [The shim](#portability) would lift this requirement, as it - positions host events by the guest's waits rather than by the executor's steps. + changes nothing it can observe. [The shim](#portability) would lift this requirement. ### Steps @@ -346,7 +340,10 @@ Replay has to know when to perform each host line. There are three kinds of line a STARTED call, the items of a `Write`, a future's `Value`. Also the host taking what the guest wrote, a `Read`, which completes the guest's write; the host dropping its end, a `Drop`, which the guest sees as `dropped`; a `Cancelled`; and the host calling an export, below. The executor - performs them when the host gets to them, so `at: n` says they happened in step `n`. In replay, + performs them when the host gets to them, so `at: n` says they happened in step `n`. For a copy, + it is the step in which the host made its side, e.g. issued the write the guest then read, + rather than the one in which the host saw it complete, which may come after the guest reacted + to it. In replay, the executor counts its steps the same way, and a job holding such a line waits until the count reaches `n`. There is no host to wake the executor, so a job waiting for a later step makes the executor wake itself, by yielding, and its next run is the next step. @@ -357,7 +354,10 @@ function that increments the counter and returns it, as the export `Call`'s `at` later runs are numbered after it. In replay, `start` makes an export call recorded at step `n` once the count reaches `n - 1`, by calling `step` and then the export. An arrival can't fall within a run of the executor: the host only calls an export while no wasm is running, unless something -suspends where the proxy can't see it, which the requirements below rule out. +suspends where the proxy can't see it, which the requirements below rule out. The exports +component's other host lines take a step the same way: those of its jobs, e.g. reading a stream an +export returned, which run on the task of `start`, not in the executor, and the host's drops of +the guest's resources. ### Why the step is enough, and a gate isn't needed @@ -403,15 +403,15 @@ trace, the two results could reach the guest before or after a different one of - **Deterministic runtime choices**, see [Portability](#portability). - **The same executor in record and replay**, with only the leaves different: host calls in - record, futures that the trace makes ready in replay. + record, futures that the trace makes ready in replay. Its work in a step depends only on its + state and the trace's lines for that step. - **Steps in replay where the host woke the executor in the recording.** Replay has no host, so a job that waits for a later step makes the executor yield, and come back as the next step. wasmtime runs the ready tasks first, as it did before the host's completion in the recording. - **No suspension the proxy can't see**: no calls to other components where the recording calls the host, no blocking WASI 0.2 calls, and no epoch deadlines that yield, as above. -- **One clock for both components.** The host calls exports through the exports component, not - the executor, so each arrival takes a step from the same counter, through `step` on - `proxy:conversion/executor`, as [Steps](#steps) describes. +- **One clock for both components.** The exports component takes its steps from the same + counter, through `step`, as [Steps](#steps) describes. ### What recording can't reach @@ -493,9 +493,9 @@ guest's wakeups. as STARTED, and the guest got the two results in separate wakeups, while replay gave them together. wasmtime runs the proxy's call into a component as a queued work item, which suspends the proxy while it is entered, and holds back the guest's next call into it (`do_not_enter`, - `concurrent.rs:774`). Calling a - host component in replay as well made other scenarios diverge, as a component host suspends - differently in record and replay. Removing it, i.e. forwarding to host functions, fixed it. + `concurrent.rs:774`). Calling a host component in replay as well made other scenarios diverge, + as a component host suspends differently in record and replay. Removing it, i.e. forwarding to + host functions, fixed it. - **Sync WASI 0.2 calls change the schedule.** At first the proxy and the guest printed each line with `println!`, and several scenarios diverged; buffering the output fixed them. - **Two results never reached the guest in one wakeup**, through the proxy, nor, in one-off @@ -546,21 +546,19 @@ trace records: the classic condition for deterministic replay, that every nondet is recorded. Here it is an induction on steps: if record and replay are in the same state when step `n` begins, and step `n` performs the same host lines, they are in the same state when step `n + 1` begins. Between steps, the guest's lines are checked, and its calls answered by the lines -without an `at`. The induction holds under four assumptions, none of them proven yet: +without an `at`. The induction holds under the requirements of +[Why the step is enough](#why-the-step-is-enough-and-a-gate-isnt-needed), and two more, none of +them proven yet: - **The executor is the only way in.** Every host event reaches the guest in a run of the executor; see [Recording](#the-executor-delivers-and-records-host-events). -- **Between steps, everything is deterministic.** The runtime's choices are fixed, and nothing - suspends where the proxy can't see it: no host components, no blocking WASI 0.2 calls, no epoch - deadlines that yield. The - component model doesn't guarantee this (`Concurrency.md` allows nondeterminism); wasmtime 49 - happens to, which is why replay in another runtime needs [the profile or the - shim](#portability). -- **The same executor in record and replay**, whose work in a step depends only on its state and - the trace's lines for that step. - **The trace holds every input**: the values, RETURNED or STARTED, and the step of each host line. +Of the requirements, deterministic runtime choices are the one the component model doesn't +guarantee (`Concurrency.md` allows nondeterminism); wasmtime 49 happens to make them, which is why +replay in another runtime needs [the profile or the shim](#portability). + ### Fork-join program In a fork-join program without determinacy races, every schedule gives the same result (the @@ -642,9 +640,8 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla 1. **Spike, in wasmtime**: done, see [Spike results](#spike-results). 2. **Trace format** in `crates/trace`, and the recorder interface: done, for p2 record and replay. -3. **Replay**, tested on traces that fuzz mode writes in this format, as its executor can count - steps too, and its mocks can label streams as record does, with the checks of - [Correctness](#checking-the-implementation). +3. **Replay**, tested on traces that fuzz mode writes in this format, with the checks of + [Correctness](#checking-the-implementation). Fuzz mode writes the format: done. 4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. 5. **Portability across runtimes**: the profile proposal, or the shim. diff --git a/src/ast.rs b/src/ast.rs index bb38f3f..3c7afdc 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -56,7 +56,11 @@ impl<'a> Opt<'a> { ident(self.mode.to_str()) )); } - Mode::Fuzz => out.push_str("import proxy:util/debug;\n"), + // `debug` for random numbers, and the trace, as in record mode. + Mode::Fuzz => { + out.push_str("import proxy:util/debug;\n"); + out.push_str("import proxy:recorder/%record@0.1.0;\n"); + } Mode::Dialog => out.push_str("import proxy:util/dialog;\n"), } } diff --git a/src/codegen/dialog.rs b/src/codegen/dialog.rs index db33590..5ca0dbd 100644 --- a/src/codegen/dialog.rs +++ b/src/codegen/dialog.rs @@ -17,12 +17,7 @@ impl State { let args = extract_arg_info(sig); let ret_ty = get_return_type(&sig.output); // The streams and futures the guest gives the host, whose reading the user decides. - let consumed: Vec<_> = args - .iter() - .zip(&wit_func.params_async) - .filter(|(arg, is_async)| **is_async && !arg.is_borrowed) - .map(|(arg, _)| &arg.ident) - .collect(); + let consumed = wit_func.consumed_args(&args); let params = params_to_wave(kind.as_ref(), &args); let preamble = quote! { #params @@ -31,18 +26,14 @@ impl State { }; if ret_ty.is_some() { let ret_name = wit_func.result.as_ref().unwrap(); - // A constructor's mock has no handle yet, so `_resource_new` prints its `ret`. - let (read_ret, print_ret) = match wit_func.constructor_resource() { - Some(name) => ( - quote! { MockedResource { name: #name.to_string(), ..Dialog::read_value(0) } }, - quote! { PENDING_RET.with(|pending| pending.set(true)); }, - ), - None => ( - quote! { Dialog::read_value(0) }, - quote! { - proxy::util::dialog::print(0, &format!("ret: {}", wasm_wave::to_string(&ToValue::to_value(&ret)).unwrap())); - }, - ), + let read_ret = match wit_func.constructor_resource() { + Some(name) => { + quote! { MockedResource { name: #name.to_string(), ..Dialog::read_value(0) } } + } + None => quote! { Dialog::read_value(0) }, + }; + let print_ret = quote! { + proxy::util::dialog::print(0, &format!("ret: {}", wasm_wave::to_string(&ToValue::to_value(&ret)).unwrap())); }; parse_quote! { #sig { diff --git a/src/codegen/fuzz.rs b/src/codegen/fuzz.rs index 0805363..4e2617c 100644 --- a/src/codegen/fuzz.rs +++ b/src/codegen/fuzz.rs @@ -17,77 +17,88 @@ impl State { let args = extract_arg_info(sig); let ret_ty = get_return_type(&sig.output); // The streams and futures the guest gives the host, which the host reads or drops. - let consumed: Vec<_> = args + let consumed = wit_func.consumed_args(&args); + // The other resources the arguments own, which the guest moved to the host. + let owned: Vec<_> = args .iter() + .zip(&wit_func.params_own) .zip(&wit_func.params_async) - .filter(|(arg, is_async)| **is_async && !arg.is_borrowed) - .map(|(arg, _)| &arg.ident) + .filter(|((arg, owns), is_async)| **owns && !**is_async && !arg.is_borrowed) + .map(|((arg, _), _)| &arg.ident) .collect(); + let drop_owned = if owned.is_empty() { + quote! {} + } else { + quote! { drop((#(#owned,)*)); } + }; let is_async = wit_func.is_async; - if ret_ty.is_none() && consumed.is_empty() && !is_async { - return parse_quote! { - #[allow(unused_variables)] - #sig {} - }; - } let params = params_to_wave(kind.as_ref(), &args); // What the host does, rather than returns: how it reads the streams and futures, and when - // an async import returns, at once (RETURNED), or after the guest and the host's - // background work ran a while (STARTED). Random, rather than drawn from the text of the - // arguments, whose first byte is mostly the `#` of a label. - let host = if consumed.is_empty() && !is_async { - quote! {} - } else { - let delay = if is_async { - quote! { - for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { - wit_bindgen::yield_async().await; - } - } - } else { - quote! {} - }; + // an async import returns, at once (RETURNED), or after the executor ran a few times + // (STARTED), in whose last run the `return` happens. Random, rather than drawn from the + // text of the arguments, whose first byte is mostly the `#` of a label. + let delay = if is_async { quote! { - let __random = proxy::util::debug::get_random(); - let mut __host = Unstructured::new(&__random); - #(Fuzz::consume(#consumed, &mut __host);)* - #delay + let __runs = __host.int_in_range(0..=3u8).unwrap_or(0); + let __at = if __runs > 0 { + proxy::recorder::record::record(&Line { by: Party::Host, at: None, event: Event::Started(__id) }); + Some(wait_for_runs(__runs as u32).await) + } else { + None + }; } + } else { + quote! { let __at: Option = None; } }; let preamble = quote! { #params - proxy::util::debug::print(&format!("import: {}({})", #display_name, __params.join(","))); - #host + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record(&trace_call(Party::Guest, __id, #display_name, #is_async, __params.clone())); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + // Dropping what the guest moved to the host isn't a drop of the guest's, so it's + // done before the call can wait, while no other task runs. + MOVING.with(|moving| moving.set(true)); + #(Fuzz::consume(#consumed, &mut __host);)* + #drop_owned + MOVING.with(|moving| moving.set(false)); + #delay + }; + let record_ret = |value: proc_macro2::TokenStream| { + quote! { + proxy::recorder::record::record(&Line { at: __at, ..trace_return(Party::Host, __id, #value) }); + } }; if ret_ty.is_some() { - // A constructor's mock has no handle yet, so `_resource_new` prints its `ret`. - let (gen_ret, print_ret) = match wit_func.constructor_resource() { - Some(name) => ( - quote! { MockedResource { name: #name.to_string(), ..Fuzz::fuzz(&mut u).unwrap() } }, - quote! { PENDING_RET.with(|pending| pending.set(true)); }, - ), - None => ( - quote! { Fuzz::fuzz(&mut u).unwrap() }, - quote! { - let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); - }, - ), + let gen_ret = match wit_func.constructor_resource() { + Some(name) => { + quote! { MockedResource { name: #name.to_string(), ..Fuzz::fuzz(&mut u).unwrap() } } + } + None => quote! { Fuzz::fuzz(&mut u).unwrap() }, + }; + let record_ret = record_ret(quote! { Some(res_str) }); + let ret = quote! { + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + #record_ret }; parse_quote! { + #[allow(unused_variables)] #sig { #preamble let __buf = __params.join(",") + #display_name; let mut u = Unstructured::new(&__buf.as_bytes()); let res = #gen_ret; - #print_ret + #ret res } } } else { + let record_ret = record_ret(quote! { None }); parse_quote! { + #[allow(unused_variables)] #sig { #preamble + #record_ret } } } @@ -97,19 +108,26 @@ impl State { .export_funcs() .into_iter() .map(|func| { - let call = func.call(); let ExportFunc { display_name, + func, arg_names, arg_tys, - ret_async, + call_params, + has_ret, + is_async, .. } = func; - // The streams and futures the guest returns, which the host reads or drops. - let ret = if ret_async { - quote! { Fuzz::consume(#call, &mut u); } + // An async call that blocks gives the host a subtask, which the guest started. + let call = if is_async { + quote! { call_export(__id, #func(#(#call_params),*)).await } + } else { + quote! { #func(#(#call_params),*) } + }; + let value = if has_ret { + quote! { Some(wasm_wave::to_string(&res.to_value()).unwrap()) } } else { - quote! { let _ = #call; } + quote! { None } }; quote! { { @@ -118,8 +136,22 @@ impl State { let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* - proxy::util::debug::print(&format!("export: {}({})", #display_name, __params.join(", "))); - #ret + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record(&Line { + at: Some(__at), + ..trace_call(Party::Host, __id, #display_name, #is_async, __params) + }); + let res = #call; + proxy::recorder::record::record(&trace_return(Party::Guest, __id, #value)); + // The streams and futures the guest returns, which the host reads or + // drops, and its resources, which the host drops. The host's own + // resources, and the mocks of the borrowed arguments, are the host's + // drops. + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); // Lets the guest, and the host's background work, run between calls. for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; @@ -133,6 +165,21 @@ impl State { self.generate_start( sig, quote! { + /// Calls an async export, and records that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + proxy::recorder::record::record(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); + } + poll + }) + .await + } let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -141,10 +188,6 @@ impl State { #(#idxs => #arms)* _ => unreachable!(), } - // clean up borrowed resources from input args - SCOPED_ALLOC.with(|alloc| { - alloc.borrow_mut().clear(); - }); } }, ) diff --git a/src/codegen/mock.rs b/src/codegen/mock.rs index eb8d4ed..bd77a77 100644 --- a/src/codegen/mock.rs +++ b/src/codegen/mock.rs @@ -1,65 +1,68 @@ //! The resources of the virtualized host in fuzz and dialog modes, whose mocks the imports -//! component makes: they are labeled by the handles wasmtime gives them. +//! component makes. A mock is labeled by a number the imports component gives it when it makes +//! it, counting from 1, which no other mock gets: its handle can't label it, as the handle moves +//! on with the mock, to the guest or the exports component, which frees its number, and a borrow +//! the guest passes back comes in as the mock itself, without a handle. Replay's mocks carry the +//! trace's labels. use super::State; use crate::Mode; use quote::quote; use syn::{Item, parse_quote}; impl State { - /// Overrides wit-bindgen's `_resource_new`, which makes the handle of every new mock of the - /// resource `name` of `module_path`, including those constructors return, to label the mock - /// by its handle. As a constructor returns the mock before it has a handle, the constructor - /// sets `PENDING_RET`, and the `ret` of the call is printed here. - pub fn generate_mock_resource_new( - &self, - module_path: &[String], - name: &str, - ) -> syn::ImplItemFn { - let iface = self.interfaces[module_path]; - let module = format!("[export]{}", self.resolve.id_of(iface).unwrap()); - let id = self.wit_type_id(module_path, name).unwrap(); - let link_name = format!( - "[resource-new]{}", - self.resolve.types[id].name.as_ref().unwrap() - ); - let print = match self.mode { - Mode::Fuzz => quote! { proxy::util::debug::print(&ret) }, - Mode::Dialog => quote! { proxy::util::dialog::print(0, &ret) }, - _ => unreachable!(), + /// Overrides wit-bindgen's `resource_from_raw_`, which the `[dtor]` of a mock calls, for its + /// drop: replay checks the guest's with the trace, and fuzz mode records the guest's, or the + /// host's, of a resource the guest gave it or gave back, or of a mock it made for an argument, + /// so that the trace has the end of every mock. + pub fn generate_mock_drop(&self) -> syn::ImplItemFn { + let on_drop = match self.mode { + Mode::Replay => quote! { + if !MOVING.with(|moving| moving.get()) { + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + proxy::recorder::replay::check(&trace_drop(Party::Guest, Table::Imports, name)); + } + }, + _ => quote! { + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + let line = if MOVING.with(|moving| moving.get()) { + Line { at: HOST_DROP_AT.get(), ..trace_drop(Party::Host, Table::Imports, name) } + } else { + trace_drop(Party::Guest, Table::Imports, name) + }; + proxy::recorder::record::record(&line); + }, }; parse_quote! { - unsafe fn _resource_new(val: *mut u8) -> u32 { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = #module)] - unsafe extern "C" { - #[link_name = #link_name] - fn new(_: *mut u8) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new(_: *mut u8) -> i32 { - unreachable!() - } - let handle = unsafe { new(val) } as u32; - let mock = unsafe { &mut *(val as *mut Option) } - .as_mut() - .unwrap(); - mock.handle = handle; - if PENDING_RET.with(|pending| pending.take()) { - let ret = format!("ret: {}", wasm_wave::to_string(&mock.to_value()).unwrap()); - #print; + unsafe fn resource_from_raw_(handle: *mut Option) -> Option { + // `None` if `into_inner` moved the resource out: not a drop. + if let Some(res) = *unsafe { Box::from_raw(handle) } { + #on_drop } - handle + None } } } /// Items the mocks use. pub fn mock_defs(&self) -> Vec { + // The step of the host's drops, when the exports component makes them, rather than a + // mock in answer to a call (see `host-drops`). + let host_drop_at = match self.mode { + Mode::Fuzz => quote! { + static HOST_DROP_AT: std::cell::Cell> = const { std::cell::Cell::new(None) }; + }, + _ => quote! {}, + }; let file: syn::File = parse_quote! { thread_local! { - // Set by a mocked constructor, whose `ret` `_resource_new` prints, once the mock - // it returned has a handle. - #[allow(dead_code)] - static PENDING_RET: std::cell::Cell = const { std::cell::Cell::new(false) }; + // The label of the last mock. + static MOCK_LABEL: std::cell::Cell = const { std::cell::Cell::new(0) }; + #host_drop_at + } + /// The label of a new mock. + #[allow(dead_code)] + fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() } }; file.items diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index dc3a6db..a159e2a 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -96,10 +96,14 @@ pub fn generate( state.generate_stubs(); let traits = crate::traits::TraitGenerator::new(&state).generate(); state.output.extend(traits); - if matches!(mode, Mode::Record | Mode::Replay) { + if matches!(mode, Mode::Record | Mode::Replay | Mode::Fuzz) { state.output.extend(trace_defs()); } - if matches!(mode, Mode::Replay) { + let has_executor = state.has_executor(); + if matches!(mode, Mode::Replay | Mode::Fuzz) { + state.output.extend(job_defs(mode, has_executor)); + } + if matches!(mode, Mode::Replay | Mode::Fuzz) { let defs = state.replay_defs(); state.output.extend(defs); } @@ -107,15 +111,16 @@ pub fn generate( let defs = state.mock_defs(); state.output.extend(defs); } - let exports_executor = state.exports_executor(); - let imports_executor = state.imports_executor(); let mut file = state.into_output_file(); - if exports_executor { - let executor = syn::parse_file(include_str!("../../assets/executor.rs"))?; - file.items.extend(executor.items); - } - if imports_executor { - file.items.extend(start_spawn_defs()); + // The imports component's jobs run in the executor, and the exports component's on the task + // of `start`. + match has_executor { + Some(HasExecutor::Exports) => { + let executor = syn::parse_file(include_str!("../../assets/executor.rs"))?; + file.items.extend(executor.items); + } + Some(HasExecutor::Imports) => file.items.extend(start_spawn_defs()), + None => {} } if use_custom_allocator { // dlmalloc with a memory source that grows by bytes, for `--page-size=1`. @@ -260,10 +265,9 @@ impl State { (Mode::Record, Some(_)) => { methods.push(syn::ImplItem::Fn(self.generate_record_drop(module_path))) } - (Mode::Replay, Some(_)) => methods.push(syn::ImplItem::Fn(self.generate_replay_drop())), - (Mode::Fuzz | Mode::Dialog, Some(resource)) => methods.push(syn::ImplItem::Fn( - self.generate_mock_resource_new(module_path, resource), - )), + (Mode::Replay | Mode::Fuzz, Some(_)) => { + methods.push(syn::ImplItem::Fn(self.generate_mock_drop())) + } _ => (), } parse_quote! { @@ -284,12 +288,12 @@ impl State { } else if func_name.starts_with("get_mock_") { let resource = get_return_type(&sig.output).unwrap(); let name = self.conversion_resource(&sig.ident); - // In fuzz and dialog modes, `_resource_new` labels the mock by its handle (see - // `generate_mock_resource_new`); in replay mode, by the handle in the trace. + // In fuzz and dialog modes, the mock gets the next label (see `next_mock_label`); in + // replay mode, the trace's. match self.mode { Mode::Fuzz | Mode::Dialog => quote! { let _ = handle; - #resource::new(MockedResource { handle: 0, name: #name.to_string() }) + #resource::new(MockedResource { handle: next_mock_label(), name: #name.to_string() }) }, _ => quote! { #resource::new(MockedResource { handle, name: #name.to_string() }) @@ -352,19 +356,15 @@ impl State { } res } - /// Whether this is the imports component, which exports `proxy:conversion/executor` to run its - /// jobs. - fn exports_executor(&self) -> bool { - self.module_paths - .iter() - .any(|path| path.join("::") == EXECUTOR_MODULE) - } - /// Whether this is the exports component, whose `start` runs `proxy:conversion/executor`. - fn imports_executor(&self) -> bool { + /// Whether this component exports `proxy:conversion/executor` or imports it; `None` in record + /// mode, which has no executor. + fn has_executor(&self) -> Option { let import = EXECUTOR_MODULE.strip_prefix("exports::").unwrap(); - self.module_paths - .iter() - .any(|path| path.join("::") == import) + self.module_paths.iter().find_map(|path| match path.join("::") { + path if path == EXECUTOR_MODULE => Some(HasExecutor::Exports), + path if path == import => Some(HasExecutor::Imports), + _ => None, + }) } /// The async `start` function running `body`. The imports component's jobs run in /// `proxy:conversion/executor` until the exports `start` called are done, and the jobs of this @@ -397,6 +397,15 @@ impl State { }, ("run", _) => quote! { run_executor().await }, ("stop", _) => quote! { stop_executor() }, + ("step", _) => quote! { next_step() }, + ("host_drops", Mode::Fuzz) => quote! { + MOVING.with(|moving| moving.set(dropping)); + HOST_DROP_AT.set(dropping.then(next_step)); + }, + ("host_drops", Mode::Replay) => { + quote! { MOVING.with(|moving| moving.set(dropping)) } + } + ("host_drops", _) => quote! { let _ = dropping; }, (name, _) => unreachable!("unexpected executor function {name}"), }; parse_quote! { @@ -414,7 +423,77 @@ impl State { } } -/// Helpers of record and replay modes, which make the lines of `proxy:recorder/types`. Each +/// How a component has `proxy:conversion/executor`, in the modes with `start`. +#[derive(Clone, Copy)] +enum HasExecutor { + /// It exports it, to run its jobs: the imports component. + Exports, + /// It imports it, which its `start` runs: the exports component, whose jobs run on the task + /// of `start`. + Imports, +} + +/// Items for the jobs of replay and fuzz modes, which differ by where the jobs run: the table the +/// component labels its handles in; `trace_at`, the step of a host line its jobs deliver, the +/// executor's run in the imports component, and a step of its own, as an export call's arrival, +/// in the exports component; and, in fuzz mode, `host_dropping`, which runs `f`, dropping mocks +/// for the host, e.g. those in a value a job read from the guest, so that the trace has the +/// host's drops, in the step they happen in. +fn job_defs(mode: &Mode, has_executor: Option) -> Vec { + let is_fuzz = matches!(mode, Mode::Fuzz); + let file: File = match has_executor { + Some(HasExecutor::Exports) => { + let host_dropping = if is_fuzz { + quote! { + #[allow(dead_code)] + fn host_dropping(f: impl FnOnce() -> R) -> R { + MOVING.with(|moving| moving.set(true)); + HOST_DROP_AT.set(Some(current_step())); + let res = f(); + MOVING.with(|moving| moving.set(false)); + HOST_DROP_AT.set(None); + res + } + } + } else { + quote! {} + }; + parse_quote! { + #[allow(dead_code)] + const TRACE_TABLE: Table = Table::Imports; + #[allow(dead_code)] + fn trace_at() -> u32 { current_step() } + #host_dropping + } + } + Some(HasExecutor::Imports) => { + let host_dropping = if is_fuzz { + quote! { + #[allow(dead_code)] + fn host_dropping(f: impl FnOnce() -> R) -> R { + proxy::conversion::executor::host_drops(true); + let res = f(); + proxy::conversion::executor::host_drops(false); + res + } + } + } else { + quote! {} + }; + parse_quote! { + #[allow(dead_code)] + const TRACE_TABLE: Table = Table::Exports; + #[allow(dead_code)] + fn trace_at() -> u32 { proxy::conversion::executor::step() } + #host_dropping + } + } + None => return Vec::new(), + }; + file.items +} + +/// Helpers of record, replay and fuzz modes, which make the lines of `proxy:recorder/types`. Each /// component numbers the calls it sees with its own counter: the exports component the host's /// calls of exports, and the imports component the guest's calls of imports (see /// `crates/trace`). diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 0c887a3..fe7f48b 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -68,32 +68,16 @@ impl State { } } } - /// Overrides wit-bindgen's `resource_from_raw_`, which the `[dtor]` of a mocked resource - /// calls, to check the guest's drop with the trace. - pub fn generate_replay_drop(&self) -> syn::ImplItemFn { - parse_quote! { - unsafe fn resource_from_raw_(handle: *mut Option) -> Option { - // `None` if `into_inner` moved the resource out: not a drop. - if let Some(res) = *unsafe { Box::from_raw(handle) } { - if !MOVING.with(|moving| moving.get()) { - let name = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::check(&trace_drop(Party::Guest, Table::Imports, name)); - } - } - None - } - } - } /// Items the replay functions use. pub fn replay_defs(&self) -> Vec { // `MOVING` is set while an import drops the resources its arguments own, which the guest - // moved to the host, so that `generate_replay_drop` doesn't check them as drops. + // moved to the host, so that `generate_mock_drop` doesn't take them for the guest's drops. // - // Only replay needs it. In record mode, the import passes the host resource of each - // argument on to the host, taking it out with `into_inner` (see `ToProxy`), so the - // destructor finds no resource to record. In replay there's no host to take them: the + // Only replay and fuzz modes need it. In record mode, the import passes the host resource + // of each argument on to the host, taking it out with `into_inner` (see `ToProxy`), so the + // destructor finds no resource to record. With mocks there's no host to take them: the // import drops its arguments, and the destructors find their mocked resources. A flag - // spares replay a type-directed trait like `ToProxy`, to take the resources out of nested + // spares a type-directed trait like `ToProxy`, to take the resources out of nested // arguments, e.g. `list`. A thread-local, as `[dtor]`s take no other arguments. let file: syn::File = parse_quote! { thread_local! { diff --git a/src/codegen/wit.rs b/src/codegen/wit.rs index be2c1c2..18a95ec 100644 --- a/src/codegen/wit.rs +++ b/src/codegen/wit.rs @@ -3,7 +3,7 @@ //! wit-bindgen names each Rust item after a WIT item, so an item of the bindings is found by //! applying wit-bindgen's naming to the WIT items, never by recovering WIT names from Rust ones. use super::State; -use super::util::ResourceFuncKind; +use super::util::{ArgInfo, ResourceFuncKind}; use crate::wit_util::{has_async_value, strip_wrapped}; use heck::ToUpperCamelCase; use std::collections::BTreeMap; @@ -40,6 +40,15 @@ pub struct WitFunc { pub result_async: bool, } impl WitFunc { + /// The arguments with the streams and futures the guest gives the host, which the + /// virtualized host reads or drops, of `args`, the arguments of the Rust function. + pub fn consumed_args<'a>(&self, args: &'a [ArgInfo]) -> Vec<&'a Ident> { + args.iter() + .zip(&self.params_async) + .filter(|(arg, is_async)| **is_async && !arg.is_borrowed) + .map(|(arg, _)| &arg.ident) + .collect() + } /// In virtualized components, constructors return `Self`, i.e. `MockedResource`, so /// type-directed traits like `Dialog` or `Arbitrary` cannot recover the resource name. /// Returns the name to patch into the `MockedResource`, if this is a constructor. diff --git a/src/instrument.rs b/src/instrument.rs index 297e628..7280bed 100644 --- a/src/instrument.rs +++ b/src/instrument.rs @@ -23,10 +23,10 @@ pub struct InstrumentArgs { pub one_byte_page_size: bool, } impl InstrumentArgs { - /// Whether the composition has the bundled recorder component: only record and replay - /// modes use it, unless the host implements it. + /// Whether the composition has the bundled recorder component: record, replay and fuzz modes + /// use it, unless the host implements it. pub fn bundles_recorder(&self) -> bool { - matches!(self.mode, Mode::Record | Mode::Replay) && !self.use_host_recorder + matches!(self.mode, Mode::Record | Mode::Replay | Mode::Fuzz) && !self.use_host_recorder } } diff --git a/src/traits/dialog.rs b/src/traits/dialog.rs index c8a0ef4..0862497 100644 --- a/src/traits/dialog.rs +++ b/src/traits/dialog.rs @@ -1,6 +1,6 @@ use crate::codegen::util::make_path; use crate::codegen::wit::WitType; -use crate::traits::{Trait, new_mock}; +use crate::traits::{Trait, host_end_defs, new_mock}; use quote::quote; use syn::{Item, ItemEnum, ItemStruct, parse_quote}; @@ -47,9 +47,8 @@ impl Trait for DialogTrait { res.push(parse_quote! { impl Dialog for #resource_path { fn read_value(_dep: u32) -> Self { - // `_resource_new` gives it its handle. #resource_path::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: #wit_name.to_string(), }) } @@ -239,9 +238,9 @@ impl Trait for DialogTrait { } impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { - // A constructor's, which `_resource_new` gives its handle. + // A constructor's. Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), } } @@ -305,13 +304,14 @@ impl Trait for DialogTrait { impl DialogTrait { /// `Dialog` for streams, futures and error-contexts. The bindings only have the `wit_stream` - /// and `wit_future` modules for them if the world has streams or futures. + /// and `wit_future` modules for them if the world has streams or futures. They are labeled as + /// in fuzz mode, by the end the component keeps (see `host_end_defs`). /// /// The virtualized host writes the streams and futures it gives the guest, and reads those /// the guest gives it, in jobs of `assets/executor.rs`, which ask the user, one chunk at a /// time, what to write, or whether to keep reading. fn async_defs(&self) -> Vec { - let mut items = Vec::new(); + let mut items = host_end_defs(self.has_streams, self.has_futures); if self.has_streams { let ast: syn::File = parse_quote! { impl Dialog for wit_bindgen::StreamReader @@ -319,12 +319,7 @@ impl DialogTrait { T: Dialog + ToValue + ValueTyped + wit_stream::StreamPayload + 'static, { fn read_value(_dep: u32) -> Self { - let (mut writer, reader) = wit_stream::new::(); - // The host keeps the writer, so it labels the stream for its lifetime; the - // reader moves on to the guest, which frees its handle. - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::dialog::print(0, &format!("new (writer: {label}, reader: {reader_label})")); + let (mut writer, reader, label) = host_stream::(); spawn(async move { let choices = ["write a chunk".to_string(), "end the stream".to_string()]; loop { @@ -345,7 +340,7 @@ impl DialogTrait { reader } fn consume(mut self, _dep: u32) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = handle_label(&self); spawn(async move { let choices = ["read a chunk".to_string(), "drop the stream".to_string()]; loop { @@ -375,12 +370,7 @@ impl DialogTrait { T: Dialog + ToValue + ValueTyped + wit_future::FuturePayload, { fn read_value(_dep: u32) -> Self { - let (writer, reader) = - wit_future::new::(|| unreachable!("the value is always written")); - // As for streams, the host keeps the writer, which labels the future. - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::dialog::print(0, &format!("new (writer: {label}, reader: {reader_label})")); + let (writer, reader, label) = host_future::(); spawn(async move { proxy::util::dialog::print(0, &format!("The host writes {label}")); let value: T = Dialog::read_value(1); @@ -394,7 +384,7 @@ impl DialogTrait { reader } fn consume(self, _dep: u32) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = handle_label(&self); spawn(async move { let choices = ["read the value".to_string(), "drop the future".to_string()]; let prompt = format!("The host reads {label}"); diff --git a/src/traits/fuzz.rs b/src/traits/fuzz.rs index ece9e8e..7c0d81b 100644 --- a/src/traits/fuzz.rs +++ b/src/traits/fuzz.rs @@ -1,6 +1,6 @@ use crate::codegen::util::make_path; use crate::codegen::wit::WitType; -use crate::traits::{Trait, new_mock}; +use crate::traits::{Trait, host_end_defs, new_mock}; use quote::quote; use syn::{Item, ItemEnum, ItemStruct, parse_quote}; @@ -25,11 +25,25 @@ impl Trait for FuzzTrait { if in_import { // The imports component, which makes the mock, labels it by its handle. let mock = new_mock(wit, quote! { 0 }); + // A resource of the guest, which the host gets from an export, and drops; the host + // keeps its own resources, which the guest gives back, as no one sees their drop. + let consume = if wit.conversion.is_none() { + quote! { + fn consume(self, _u: &mut Unstructured<'_>) { + let name = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let line = trace_drop(Party::Host, TRACE_TABLE, name); + proxy::recorder::record::record(&Line { at: Some(trace_at()), ..line }); + } + } + } else { + quote! {} + }; res.push(parse_quote! { impl Fuzz for #resource_path { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(#mock) } + #consume } }); res.push(parse_quote! { @@ -47,9 +61,8 @@ impl Trait for FuzzTrait { res.push(parse_quote! { impl Fuzz for #resource_path { fn fuzz(_u: &mut Unstructured<'_>) -> Result { - // `_resource_new` gives it its handle. Ok(#resource_path::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: #wit_name.to_string(), })) } @@ -89,6 +102,10 @@ impl Trait for FuzzTrait { #(#field_names: Fuzz::fuzz(u)?),* }) } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + #(Fuzz::consume(self.#field_names, u);)* + } } }] } @@ -112,6 +129,14 @@ impl Trait for FuzzTrait { syn::Fields::Named(_) => unreachable!(), } }); + let consume_arms = enum_item.variants.iter().map(|variant| { + let tag = &variant.ident; + match &variant.fields { + syn::Fields::Unit => quote! { #enum_name::#tag => {} }, + syn::Fields::Unnamed(_) => quote! { #enum_name::#tag(x) => Fuzz::consume(x, u) }, + syn::Fields::Named(_) => unreachable!(), + } + }); let variant_len = enum_item.variants.len(); vec![parse_quote! { impl #impl_generics Fuzz for #enum_name #ty_generics #where_clause { @@ -122,6 +147,12 @@ impl Trait for FuzzTrait { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + #(#consume_arms),* + } + } } }] } @@ -160,16 +191,17 @@ impl Trait for FuzzTrait { trait Fuzz: Sized { // A value the virtualized host gives the guest. fn fuzz(u: &mut Unstructured<'_>) -> Result; - // What the virtualized host does with a value the guest gives it: by default, drop it. - // Only streams and futures override it, which p2 worlds don't have. + // What the virtualized host does with a value the guest gives it: by default, drop + // it. Streams and futures override it, and the guest's resources, whose drop the + // host records; records, variants and the like pass it on to their parts. #[allow(dead_code)] fn consume(self, _u: &mut Unstructured<'_>) {} } impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { - // A constructor's, which `_resource_new` gives its handle. + // A constructor's. Ok(Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), }) } @@ -255,45 +287,71 @@ impl Trait for FuzzTrait { impl FuzzTrait { /// `Fuzz` for streams, futures and error-contexts. The bindings only have the `wit_stream` /// and `wit_future` modules for them if the world has streams or futures. Their labels, e.g. - /// `#stream-1`, are their handles, as for resources. + /// `#stream-1`, are the handles of the ends the component keeps: the reader of one the guest + /// gives, and the writer of one the host gives, which `label_by_writer` also prints its + /// reader by. /// /// The virtualized host writes the streams and futures it gives the guest, and reads those - /// the guest gives it, in jobs of `assets/executor.rs`. What it writes, and how it reads, - /// e.g. whether it reads to the end, is fuzzed when the value changes hands. + /// the guest gives it, in jobs of `assets/executor.rs`, which record each copy, value and + /// drop. What it writes, and how it reads, e.g. whether it reads to the end, is fuzzed when + /// the value changes hands. fn async_defs(&self) -> Vec { - let mut items = Vec::new(); + let mut items = host_end_defs(self.has_streams, self.has_futures); + if self.has_streams || self.has_futures { + let ast: syn::File = parse_quote! { + /// The label of a stream or future, by the end this component keeps. + fn label_of(end: &impl ToValue) -> Label { + Label { table: TRACE_TABLE, name: handle_label(end) } + } + /// A host line, in step `at`, in which its job made it, e.g. issued the write the + /// guest read; or, by default, in the current one. + fn host_line_at(at: u32, event: Event) -> Line { + Line { by: Party::Host, at: Some(at), event } + } + fn host_line(event: Event) -> Line { + host_line_at(trace_at(), event) + } + fn guest_line(event: Event) -> Line { + Line { by: Party::Guest, at: None, event } + } + }; + items.extend(ast.items); + } if self.has_streams { let ast: syn::File = parse_quote! { impl Fuzz for wit_bindgen::StreamReader where T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, { - // Writes fuzzed chunks, then ends the stream. + // Writes fuzzed chunks, then ends the stream. A line is a chunk, which the + // guest may read in several copies. fn fuzz(u: &mut Unstructured<'_>) -> Result { - let (mut writer, reader) = wit_stream::new::(); - // The host keeps the writer, so it labels the stream for its lifetime; the - // reader moves on to the guest, which frees its handle. - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::debug::print(&format!("new (writer: {label}, reader: {reader_label})")); + let (mut writer, reader, name) = host_stream::(); + let label = Label { table: TRACE_TABLE, name }; let chunks: Vec> = Fuzz::fuzz(u)?; spawn(async move { - for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { + let mut chunks = chunks.into_iter().filter(|chunk| !chunk.is_empty()); + while let Some(chunk) = chunks.next() { let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); - if !writer.write_all(chunk).await.is_empty() { - proxy::util::debug::print(&format!("{label} dropped by guest")); + let at = trace_at(); + let rest = writer.write_all(chunk).await; + if !rest.is_empty() { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((rest, chunks.collect::>()))); return; } - proxy::util::debug::print(&format!("{label}.read() = {items}")); + let chunk = Chunk { stream: label.clone(), items }; + proxy::recorder::record::record(&host_line_at(at, Event::Write(chunk))); } - proxy::util::debug::print(&format!("{label}.read() = dropped")); + drop(writer); + proxy::recorder::record::record(&host_line(Event::Drop(label))); }); Ok(reader) } // Mostly reads to the end; otherwise reads a few chunks, or none, and drops the // stream. Out of fuzz data, it reads to the end. fn consume(mut self, u: &mut Unstructured<'_>) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = label_of(&self); let reads: Option = match u.int_in_range(0..=9u8).unwrap_or(0) { 8 => Some(u.int_in_range(1..=4).unwrap_or(1)), 9 => Some(0), @@ -303,16 +361,20 @@ impl FuzzTrait { spawn(async move { let mut count = 0; while reads != Some(count) { + let at = trace_at(); let (result, items) = self.read(Vec::with_capacity(capacity)).await; if let wit_bindgen::StreamResult::Dropped = result { - proxy::util::debug::print(&format!("{label} -> dropped")); + proxy::recorder::record::record(&guest_line(Event::Drop(label))); return; } - let items = wasm_wave::to_string(&items.to_value()).unwrap(); - proxy::util::debug::print(&format!("{label} -> {items}")); + let wave = wasm_wave::to_string(&items.to_value()).unwrap(); + let chunk = Chunk { stream: label.clone(), items: wave }; + proxy::recorder::record::record(&host_line_at(at, Event::Read(chunk))); + host_dropping(move || drop(items)); count += 1; } - proxy::util::debug::print(&format!("{label} dropped by host")); + drop(self); + proxy::recorder::record::record(&host_line(Event::Drop(label))); }); } } @@ -328,33 +390,38 @@ impl FuzzTrait { // Writes a fuzzed value. fn fuzz(u: &mut Unstructured<'_>) -> Result { let value = T::fuzz(u)?; - let (writer, reader) = - wit_future::new::(|| unreachable!("the value is always written")); - // As for streams, the host keeps the writer, which labels the future. - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::debug::print(&format!("new (writer: {label}, reader: {reader_label})")); + let (writer, reader, name) = host_future::(); + let label = Label { table: TRACE_TABLE, name }; let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { - if writer.write(value).await.is_ok() { - proxy::util::debug::print(&format!("{label}.read() = {wave}")); - } else { - proxy::util::debug::print(&format!("{label} dropped by guest")); + let at = trace_at(); + match writer.write(value).await { + Ok(()) => { + let value = FutureValue { future: label, value: wave }; + proxy::recorder::record::record(&host_line_at(at, Event::Value(value))); + } + Err(unwritten) => { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); + } } }); Ok(reader) } - // Mostly reads the value; otherwise drops the future. + // Mostly reads the value, which the guest wrote; otherwise drops the future. fn consume(self, u: &mut Unstructured<'_>) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = label_of(&self); let read = u.int_in_range(0..=9u8).unwrap_or(0) != 9; spawn(async move { if read { - let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); - proxy::util::debug::print(&format!("{label} -> {value}")); + let value = self.await; + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + let line = guest_line(Event::Value(FutureValue { future: label, value: wave })); + proxy::recorder::record::record(&line); + host_dropping(move || drop(value)); } else { drop(self); - proxy::util::debug::print(&format!("{label} dropped by host")); + proxy::recorder::record::record(&host_line(Event::Drop(label))); } }); } diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 6877122..0b2c744 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -40,6 +40,76 @@ fn new_mock(wit: &WitType, handle: TokenStream) -> TokenStream { } } +/// Items for the virtualized host's ends of the streams and futures it gives the guest, which +/// the fuzz and dialog traits make: `host_stream` and `host_future` make the pair, keep its +/// writer, which labels it for its lifetime, and hand out its reader. The bindings only have the +/// `wit_stream` and `wit_future` modules if the world has streams or futures. +fn host_end_defs(has_streams: bool, has_futures: bool) -> Vec { + let mut items = Vec::new(); + if !has_streams && !has_futures { + return items; + } + let file: syn::File = syn::parse_quote! { + thread_local! { + // The readers of the streams and futures this component made, by handle, and the + // writers that label them (see `label_by_writer`). + static READER_LABELS: std::cell::RefCell> = + std::cell::RefCell::new(std::collections::HashMap::new()); + } + /// Labels a stream or future this component made by the handle of its `writer`, which it + /// keeps for the stream's lifetime, also where its `reader` is printed: in the value that + /// hands the reader out, after which the reader moves on, and its handle can be reused. + /// So the entry is gone once the reader is printed. + fn label_by_writer(reader: u32, writer: u32) { + static HOOK: std::sync::Once = std::sync::Once::new(); + HOOK.call_once(|| { + wasm_wave::value::convert::set_handle_label(|_kind, handle| { + READER_LABELS.with_borrow_mut(|labels| labels.remove(&handle)) + }) + }); + READER_LABELS.with_borrow_mut(|labels| { + // A handle the new writer reuses is no longer a reader's. + labels.remove(&writer); + labels.insert(reader, writer); + }); + } + /// The label of a stream or future, e.g. `#stream-7`, by the end this component keeps. + fn handle_label(end: &impl ToValue) -> String { + wasm_wave::to_string(&end.to_value()).unwrap() + } + }; + items.extend(file.items); + if has_streams { + let file: syn::File = syn::parse_quote! { + /// A stream the host writes: its writer, its reader, to hand out, and its label. + fn host_stream( + ) -> (wit_bindgen::StreamWriter, wit_bindgen::StreamReader, String) { + let (writer, reader) = wit_stream::new::(); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) + } + }; + items.extend(file.items); + } + if has_futures { + let file: syn::File = syn::parse_quote! { + /// A future the host writes, which it always does: its writer, its reader, to hand + /// out, and its label. + fn host_future( + ) -> (wit_bindgen::FutureWriter, wit_bindgen::FutureReader, String) { + let (writer, reader) = + wit_future::new::(|| unreachable!("the value is always written")); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) + } + }; + items.extend(file.items); + } + items +} + pub trait Trait { fn trait_defs(&self) -> Vec; // `wit` has the WIT names of the item and of its fields, cases or flags, in order. diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index 5ea6d76..be387ea 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -854,7 +854,7 @@ impl Dialog for Result { impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), } } @@ -897,8 +897,13 @@ impl Dialog for wit_bindgen::rt::async_support::ErrorContext { } } thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { - std::cell::Cell::new(false) }; + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() } thread_local! { static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index 53a84ce..8d2eb20 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -8,7 +8,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { ) -> exports::proxy::conversion::conversion::TestHostTypesCounter { let _ = handle; exports::proxy::conversion::conversion::TestHostTypesCounter::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "counter".to_string(), }) } @@ -26,6 +26,12 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn stop() -> () { stop_executor() } + fn step() -> u32 { + next_step() + } + fn host_drops(dropping: bool) -> () { + let _ = dropping; + } } impl exports::test::host::types::Guest for Stub { type Counter = MockedResource; @@ -114,7 +120,12 @@ impl exports::test::host::types::GuestCounter for MockedResource { name: "counter".to_string(), ..Dialog::read_value(0) }; - PENDING_RET.with(|pending| pending.set(true)); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& ret)).unwrap() + ), + ); ret } fn get(&self) -> u32 { @@ -164,30 +175,6 @@ impl exports::test::host::types::GuestCounter for MockedResource { ); ret } - unsafe fn _resource_new(val: *mut u8) -> u32 { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "[export]test:host/types@0.1.0")] - unsafe extern "C" { - #[link_name = "[resource-new]counter"] - fn new(_: *mut u8) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new(_: *mut u8) -> i32 { - unreachable!() - } - let handle = unsafe { new(val) } as u32; - let mock = unsafe { &mut *(val as *mut Option) } - .as_mut() - .unwrap(); - mock.handle = handle; - if PENDING_RET.with(|pending| pending.take()) { - let ret = format!( - "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() - ); - proxy::util::dialog::print(0, &ret); - } - handle - } } impl exports::test::log0_1_0::sink::Guest for Stub { #[allow(unused_variables)] @@ -564,7 +551,7 @@ impl Dialog for exports::test::host::types::Perms { impl Dialog for exports::test::host::types::Counter { fn read_value(_dep: u32) -> Self { exports::test::host::types::Counter::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "counter".to_string(), }) } @@ -643,7 +630,7 @@ impl Dialog for Result { impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), } } @@ -686,8 +673,13 @@ impl Dialog for wit_bindgen::rt::async_support::ErrorContext { } } thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { - std::cell::Cell::new(false) }; + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() } type Job = std::pin::Pin>>; #[derive(Default)] @@ -698,7 +690,59 @@ struct Executor { } thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = + const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. A job counts them, waking the executor itself in between. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + #[derive(Default)] + struct Done { + step: Option, + waker: Option, + } + let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_done = done.clone(); + let mut left = runs; + spawn( + std::future::poll_fn(move |_| { + if left == 0 { + let mut done = job_done.borrow_mut(); + done.step = Some(current_step()); + if let Some(waker) = done.waker.take() { + waker.wake(); + } + return std::task::Poll::Ready(()); + } + left -= 1; + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }), + ); + std::future::poll_fn(move |cx| { + let mut done = done.borrow_mut(); + match done.step { + Some(step) => std::task::Poll::Ready(step), + None => { + done.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -716,6 +760,8 @@ fn spawn(job: impl std::future::Future + 'static) { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + next_step(); + WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); loop { let mut queued = EXECUTOR @@ -732,6 +778,9 @@ async fn run_executor() { std::task::Poll::Ready(()) } else { e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + cx.waker().wake_by_ref(); + } std::task::Poll::Pending } }) diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index 757fa26..edaec4e 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -5,6 +5,30 @@ bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + /// Calls an async export, and records that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export( + id: TaskId, + call: impl std::future::Future, + ) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await + } let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -16,13 +40,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:guest/api@0.1.0.run", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.run", + false, + __params, + ) + }, + ); + let res = test::guest::api::run(&p, &c); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::guest::api::run(&p, &c); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -31,13 +74,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let mut __params: Vec = Vec::new(); let s: test::guest::api::Shape = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:guest/api@0.1.0.classify", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.classify", + false, + __params, + ) + }, + ); + let res = test::guest::api::classify(&s); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::guest::api::classify(&s); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -46,13 +108,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let mut __params: Vec = Vec::new(); let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:guest/api@0.1.0.begin", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.begin", + false, + __params, + ) + }, + ); + let res = test::guest::api::begin(c); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::guest::api::begin(c); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -62,13 +143,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let name: String = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "new test:guest/api/session@0.1.0", - __params.join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "new test:guest/api/session@0.1.0", + false, + __params, + ) + }, + ); + let res = test::guest::api::Session::new(&name); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::guest::api::Session::new(&name); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -79,13 +179,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); let y: i32 = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:host/types@0.1.0.make-point", - __params.join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types@0.1.0.make-point", + false, + __params, + ) + }, + ); + let res = test::host::types::make_point(x, y); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::host::types::make_point(x, y); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -96,13 +215,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c: test::host::types::Color = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:host/types@0.1.0.check", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types@0.1.0.check", + false, + __params, + ) + }, + ); + let res = test::host::types::check(p, c); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::host::types::check(p, c); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -112,13 +250,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let name: String = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:host/types@0.1.0.open", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types@0.1.0.open", + false, + __params, + ) + }, + ); + let res = test::host::types::open(&name); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::host::types::open(&name); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -128,13 +285,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let start: u32 = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "new test:host/types/counter@0.1.0", - __params.join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "new test:host/types/counter@0.1.0", + false, + __params, + ) + }, + ); + let res = test::host::types::Counter::new(start); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::host::types::Counter::new(start); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -145,23 +321,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); let b: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:host/types/counter@0.1.0.merge", - __params.join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types/counter@0.1.0.merge", + false, + __params, + ) + }, + ); + let res = test::host::types::Counter::merge(&a, b); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::host::types::Counter::merge(&a, b); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } } _ => unreachable!(), } - SCOPED_ALLOC - .with(|alloc| { - alloc.borrow_mut().clear(); - }); } join_spawned().await; proxy::conversion::executor::stop(); @@ -389,6 +580,16 @@ impl Fuzz for test::guest::api::Session { cannot_mock() }) } + fn consume(self, _u: &mut Unstructured<'_>) { + let name = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let line = trace_drop(Party::Host, TRACE_TABLE, name); + proxy::recorder::record::record( + &Line { + at: Some(trace_at()), + ..line + }, + ); + } } impl<'a> Fuzz for &'a test::guest::api::Session { fn fuzz(_u: &mut Unstructured<'_>) -> Result { @@ -418,6 +619,12 @@ impl Fuzz for test::host::types::Point { label: Fuzz::fuzz(u)?, }) } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + Fuzz::consume(self.x, u); + Fuzz::consume(self.y, u); + Fuzz::consume(self.label, u); + } } impl Fuzz for test::host::types::Shape { fn fuzz(u: &mut Unstructured<'_>) -> Result { @@ -430,6 +637,15 @@ impl Fuzz for test::host::types::Shape { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + test::host::types::Shape::Circle(x) => Fuzz::consume(x, u), + test::host::types::Shape::Rect(x) => Fuzz::consume(x, u), + test::host::types::Shape::HttpError(x) => Fuzz::consume(x, u), + test::host::types::Shape::Empty => {} + } + } } impl Fuzz for test::host::types::Color { fn fuzz(u: &mut Unstructured<'_>) -> Result { @@ -441,6 +657,14 @@ impl Fuzz for test::host::types::Color { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + test::host::types::Color::Red => {} + test::host::types::Color::Green => {} + test::host::types::Color::DnsBlue => {} + } + } } impl Fuzz for test::host::types::Perms { fn fuzz(u: &mut Unstructured<'_>) -> Result { @@ -489,7 +713,7 @@ trait Fuzz: Sized { impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), }) } @@ -560,10 +784,96 @@ impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { Ok(Self::new(&String::fuzz(u)?)) } } +use bindings::proxy::recorder::types::*; thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Exports; +#[allow(dead_code)] +fn trace_at() -> u32 { + proxy::conversion::executor::step() +} +#[allow(dead_code)] +fn host_dropping(f: impl FnOnce() -> R) -> R { + proxy::conversion::executor::host_drops(true); + let res = f(); + proxy::conversion::executor::host_drops(false); + res +} +thread_local! { + #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } +thread_local! { + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; + static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { + std::cell::Cell::new(None) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() +} thread_local! { static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const { std::cell::RefCell::new(Vec::new()) }; diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index de6665f..d213380 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -8,7 +8,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { ) -> exports::proxy::conversion::conversion::TestHostTypesCounter { let _ = handle; exports::proxy::conversion::conversion::TestHostTypesCounter::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "counter".to_string(), }) } @@ -25,25 +25,49 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn stop() -> () { stop_executor() } + fn step() -> u32 { + next_step() + } + fn host_drops(dropping: bool) -> () { + MOVING.with(|moving| moving.set(dropping)); + HOST_DROP_AT.set(dropping.then(next_step)); + } } impl exports::test::host::types::Guest for Stub { type Counter = MockedResource; + #[allow(unused_variables)] fn make_point(x: i32, y: i32) -> exports::test::host::types::Point { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); - proxy::util::debug::print( - &format!( - "import: {}({})", "test:host/types@0.1.0.make-point", __params.join(",") + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.make-point", + false, + __params.clone(), ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:host/types@0.1.0.make-point"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } + #[allow(unused_variables)] fn check( p: exports::test::host::types::Perms, c: exports::test::host::types::Color, @@ -51,68 +75,163 @@ impl exports::test::host::types::Guest for Stub { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "test:host/types@0.1.0.check", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.check", + false, + __params.clone(), + ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:host/types@0.1.0.check"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } + #[allow(unused_variables)] fn open( name: String, ) -> ::core::result::Result { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "test:host/types@0.1.0.open", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.open", + false, + __params.clone(), + ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:host/types@0.1.0.open"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } } impl exports::test::host::types::GuestCounter for MockedResource { + #[allow(unused_variables)] fn new(start: u32) -> Self { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); - proxy::util::debug::print( - &format!( - "import: {}({})", "new test:host/types/counter@0.1.0", __params.join(",") + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "new test:host/types/counter@0.1.0", + false, + __params.clone(), ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "new test:host/types/counter@0.1.0"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = MockedResource { name: "counter".to_string(), ..Fuzz::fuzz(&mut u).unwrap() }; - PENDING_RET.with(|pending| pending.set(true)); + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } + #[allow(unused_variables)] fn get(&self) -> u32 { let mut __params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; - proxy::util::debug::print( - &format!( - "import: {}({})", "test:host/types/counter@0.1.0.get", __params.join(",") + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.get", + false, + __params.clone(), ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:host/types/counter@0.1.0.get"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } #[allow(unused_variables)] - fn add(&self, n: u32) -> () {} + fn add(&self, n: u32) -> () { + let mut __params: Vec = vec![ + wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() + ]; + __params.push(wasm_wave::to_string(&ToValue::to_value(&n)).unwrap()); + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.add", + false, + __params.clone(), + ), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, + ); + } + #[allow(unused_variables)] fn merge( a: exports::test::host::types::CounterBorrow<'_>, b: exports::test::host::types::Counter, @@ -120,60 +239,108 @@ impl exports::test::host::types::GuestCounter for MockedResource { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); __params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); - proxy::util::debug::print( - &format!( - "import: {}({})", "test:host/types/counter@0.1.0.merge", __params - .join(",") + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.merge", + false, + __params.clone(), ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + drop((b,)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:host/types/counter@0.1.0.merge"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } - unsafe fn _resource_new(val: *mut u8) -> u32 { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "[export]test:host/types@0.1.0")] - unsafe extern "C" { - #[link_name = "[resource-new]counter"] - fn new(_: *mut u8) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new(_: *mut u8) -> i32 { - unreachable!() - } - let handle = unsafe { new(val) } as u32; - let mock = unsafe { &mut *(val as *mut Option) } - .as_mut() - .unwrap(); - mock.handle = handle; - if PENDING_RET.with(|pending| pending.take()) { - let ret = format!( - "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() - ); - proxy::util::debug::print(&ret); + unsafe fn resource_from_raw_(handle: *mut Option) -> Option { + if let Some(res) = *unsafe { Box::from_raw(handle) } { + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + let line = if MOVING.with(|moving| moving.get()) { + Line { + at: HOST_DROP_AT.get(), + ..trace_drop(Party::Host, Table::Imports, name) + } + } else { + trace_drop(Party::Guest, Table::Imports, name) + }; + proxy::recorder::record::record(&line); } - handle + None } } impl exports::test::log0_1_0::sink::Guest for Stub { #[allow(unused_variables)] - fn write(msg: String) -> () {} + fn write(msg: String) -> () { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:log/sink@0.1.0.write", + false, + __params.clone(), + ), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, + ); + } } impl exports::test::log0_2_0::sink::Guest for Stub { + #[allow(unused_variables)] fn write(msg: String) -> bool { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "test:log/sink@0.2.0.write", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:log/sink@0.2.0.write", + false, + __params.clone(), + ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:log/sink@0.2.0.write"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } } @@ -371,6 +538,12 @@ impl Fuzz for exports::test::host::types::Point { label: Fuzz::fuzz(u)?, }) } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + Fuzz::consume(self.x, u); + Fuzz::consume(self.y, u); + Fuzz::consume(self.label, u); + } } impl Fuzz for exports::test::host::types::Shape { fn fuzz(u: &mut Unstructured<'_>) -> Result { @@ -383,6 +556,15 @@ impl Fuzz for exports::test::host::types::Shape { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + exports::test::host::types::Shape::Circle(x) => Fuzz::consume(x, u), + exports::test::host::types::Shape::Rect(x) => Fuzz::consume(x, u), + exports::test::host::types::Shape::HttpError(x) => Fuzz::consume(x, u), + exports::test::host::types::Shape::Empty => {} + } + } } impl Fuzz for exports::test::host::types::Color { fn fuzz(u: &mut Unstructured<'_>) -> Result { @@ -394,6 +576,14 @@ impl Fuzz for exports::test::host::types::Color { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + exports::test::host::types::Color::Red => {} + exports::test::host::types::Color::Green => {} + exports::test::host::types::Color::DnsBlue => {} + } + } } impl Fuzz for exports::test::host::types::Perms { fn fuzz(u: &mut Unstructured<'_>) -> Result { @@ -415,7 +605,7 @@ impl Fuzz for exports::test::host::types::Counter { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok( exports::test::host::types::Counter::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "counter".to_string(), }), ) @@ -436,7 +626,7 @@ trait Fuzz: Sized { impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), }) } @@ -507,10 +697,98 @@ impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { Ok(Self::new(&String::fuzz(u)?)) } } +use bindings::proxy::recorder::types::*; +thread_local! { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Imports; +#[allow(dead_code)] +fn trace_at() -> u32 { + current_step() +} +#[allow(dead_code)] +fn host_dropping(f: impl FnOnce() -> R) -> R { + MOVING.with(|moving| moving.set(true)); + HOST_DROP_AT.set(Some(current_step())); + let res = f(); + MOVING.with(|moving| moving.set(false)); + HOST_DROP_AT.set(None); + res +} thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } +thread_local! { + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; + static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { + std::cell::Cell::new(None) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() +} type Job = std::pin::Pin>>; #[derive(Default)] struct Executor { @@ -520,7 +798,59 @@ struct Executor { } thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = + const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. A job counts them, waking the executor itself in between. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + #[derive(Default)] + struct Done { + step: Option, + waker: Option, + } + let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_done = done.clone(); + let mut left = runs; + spawn( + std::future::poll_fn(move |_| { + if left == 0 { + let mut done = job_done.borrow_mut(); + done.step = Some(current_step()); + if let Some(waker) = done.waker.take() { + waker.wake(); + } + return std::task::Poll::Ready(()); + } + left -= 1; + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }), + ); + std::future::poll_fn(move |cx| { + let mut done = done.borrow_mut(); + match done.step { + Some(step) => std::task::Poll::Ready(step), + None => { + done.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -538,6 +868,8 @@ fn spawn(job: impl std::future::Future + 'static) { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + next_step(); + WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); loop { let mut queued = EXECUTOR @@ -554,6 +886,9 @@ async fn run_executor() { std::task::Poll::Ready(()) } else { e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + cx.waker().wake_by_ref(); + } std::task::Poll::Pending } }) diff --git a/tests/snapshots/fuzz/wit/component.wit b/tests/snapshots/fuzz/wit/component.wit index fab59b2..12448ea 100644 --- a/tests/snapshots/fuzz/wit/component.wit +++ b/tests/snapshots/fuzz/wit/component.wit @@ -1,6 +1,7 @@ package component:proxy; world imports { import proxy:util/debug; + import proxy:recorder/%record@0.1.0; export proxy:conversion/conversion; export proxy:conversion/executor; export test:host/types@0.1.0; @@ -13,6 +14,7 @@ world tmp-exports { } world exports { import proxy:util/debug; + import proxy:recorder/%record@0.1.0; import proxy:conversion/conversion; import proxy:conversion/executor; import test:host/types@0.1.0; diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index c05cec1..8df1ca9 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -575,7 +575,7 @@ impl Dialog for Result { impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), } } @@ -607,18 +607,62 @@ impl_dialog_tuple!(T1, T2, T3, T4, T5); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +thread_local! { + static READER_LABELS : std::cell::RefCell < std::collections::HashMap < u32, u32 >> = + std::cell::RefCell::new(std::collections::HashMap::new()); +} +/// Labels a stream or future this component made by the handle of its `writer`, which it +/// keeps for the stream's lifetime, also where its `reader` is printed: in the value that +/// hands the reader out, after which the reader moves on, and its handle can be reused. +/// So the entry is gone once the reader is printed. +fn label_by_writer(reader: u32, writer: u32) { + static HOOK: std::sync::Once = std::sync::Once::new(); + HOOK.call_once(|| { + wasm_wave::value::convert::set_handle_label(|_kind, handle| { + READER_LABELS.with_borrow_mut(|labels| labels.remove(&handle)) + }) + }); + READER_LABELS + .with_borrow_mut(|labels| { + labels.remove(&writer); + labels.insert(reader, writer); + }); +} +/// The label of a stream or future, e.g. `#stream-7`, by the end this component keeps. +fn handle_label(end: &impl ToValue) -> String { + wasm_wave::to_string(&end.to_value()).unwrap() +} +/// A stream the host writes: its writer, its reader, to hand out, and its label. +fn host_stream() -> ( + wit_bindgen::StreamWriter, + wit_bindgen::StreamReader, + String, +) { + let (writer, reader) = wit_stream::new::(); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} +/// A future the host writes, which it always does: its writer, its reader, to hand +/// out, and its label. +fn host_future() -> ( + wit_bindgen::FutureWriter, + wit_bindgen::FutureReader, + String, +) { + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} impl Dialog for wit_bindgen::StreamReader where T: Dialog + ToValue + ValueTyped + wit_stream::StreamPayload + 'static, { fn read_value(_dep: u32) -> Self { - let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::dialog::print( - 0, - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (mut writer, reader, label) = host_stream::(); spawn(async move { let choices = ["write a chunk".to_string(), "end the stream".to_string()]; loop { @@ -639,7 +683,7 @@ where reader } fn consume(mut self, _dep: u32) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = handle_label(&self); spawn(async move { let choices = ["read a chunk".to_string(), "drop the stream".to_string()]; loop { @@ -664,15 +708,7 @@ where T: Dialog + ToValue + ValueTyped + wit_future::FuturePayload, { fn read_value(_dep: u32) -> Self { - let (writer, reader) = wit_future::new::< - T, - >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::dialog::print( - 0, - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (writer, reader, label) = host_future::(); spawn(async move { proxy::util::dialog::print(0, &format!("The host writes {label}")); let value: T = Dialog::read_value(1); @@ -686,7 +722,7 @@ where reader } fn consume(self, _dep: u32) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = handle_label(&self); spawn(async move { let choices = ["read the value".to_string(), "drop the future".to_string()]; let prompt = format!("The host reads {label}"); @@ -711,8 +747,13 @@ impl Dialog for wit_bindgen::rt::async_support::ErrorContext { } } thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { - std::cell::Cell::new(false) }; + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() } thread_local! { static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index b39a9d1..4338977 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -8,7 +8,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { ) -> exports::proxy::conversion::conversion::TestP3HostBody { let _ = handle; exports::proxy::conversion::conversion::TestP3HostBody::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "body".to_string(), }) } @@ -26,6 +26,12 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn stop() -> () { stop_executor() } + fn step() -> u32 { + next_step() + } + fn host_drops(dropping: bool) -> () { + let _ = dropping; + } } impl exports::test::p3::host::Guest for Stub { type Body = MockedResource; @@ -98,7 +104,12 @@ impl exports::test::p3::host::GuestBody for MockedResource { name: "body".to_string(), ..Dialog::read_value(0) }; - PENDING_RET.with(|pending| pending.set(true)); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& ret)).unwrap() + ), + ); ret } async fn size(&self) -> u64 { @@ -156,30 +167,6 @@ impl exports::test::p3::host::GuestBody for MockedResource { ); ret } - unsafe fn _resource_new(val: *mut u8) -> u32 { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "[export]test:p3/host@0.1.0")] - unsafe extern "C" { - #[link_name = "[resource-new]body"] - fn new(_: *mut u8) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new(_: *mut u8) -> i32 { - unreachable!() - } - let handle = unsafe { new(val) } as u32; - let mock = unsafe { &mut *(val as *mut Option) } - .as_mut() - .unwrap(); - mock.handle = handle; - if PENDING_RET.with(|pending| pending.take()) { - let ret = format!( - "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() - ); - proxy::util::dialog::print(0, &ret); - } - handle - } } impl ValueTyped for exports::test::p3::host::ErrorCode { #[allow(unused_imports)] @@ -352,7 +339,7 @@ impl Dialog for exports::test::p3::host::ErrorCode { impl Dialog for exports::test::p3::host::Body { fn read_value(_dep: u32) -> Self { exports::test::p3::host::Body::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "body".to_string(), }) } @@ -431,7 +418,7 @@ impl Dialog for Result { impl Dialog for MockedResource { fn read_value(_dep: u32) -> Self { Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), } } @@ -463,18 +450,62 @@ impl_dialog_tuple!(T1, T2, T3, T4, T5); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_dialog_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +thread_local! { + static READER_LABELS : std::cell::RefCell < std::collections::HashMap < u32, u32 >> = + std::cell::RefCell::new(std::collections::HashMap::new()); +} +/// Labels a stream or future this component made by the handle of its `writer`, which it +/// keeps for the stream's lifetime, also where its `reader` is printed: in the value that +/// hands the reader out, after which the reader moves on, and its handle can be reused. +/// So the entry is gone once the reader is printed. +fn label_by_writer(reader: u32, writer: u32) { + static HOOK: std::sync::Once = std::sync::Once::new(); + HOOK.call_once(|| { + wasm_wave::value::convert::set_handle_label(|_kind, handle| { + READER_LABELS.with_borrow_mut(|labels| labels.remove(&handle)) + }) + }); + READER_LABELS + .with_borrow_mut(|labels| { + labels.remove(&writer); + labels.insert(reader, writer); + }); +} +/// The label of a stream or future, e.g. `#stream-7`, by the end this component keeps. +fn handle_label(end: &impl ToValue) -> String { + wasm_wave::to_string(&end.to_value()).unwrap() +} +/// A stream the host writes: its writer, its reader, to hand out, and its label. +fn host_stream() -> ( + wit_bindgen::StreamWriter, + wit_bindgen::StreamReader, + String, +) { + let (writer, reader) = wit_stream::new::(); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} +/// A future the host writes, which it always does: its writer, its reader, to hand +/// out, and its label. +fn host_future() -> ( + wit_bindgen::FutureWriter, + wit_bindgen::FutureReader, + String, +) { + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} impl Dialog for wit_bindgen::StreamReader where T: Dialog + ToValue + ValueTyped + wit_stream::StreamPayload + 'static, { fn read_value(_dep: u32) -> Self { - let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::dialog::print( - 0, - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (mut writer, reader, label) = host_stream::(); spawn(async move { let choices = ["write a chunk".to_string(), "end the stream".to_string()]; loop { @@ -495,7 +526,7 @@ where reader } fn consume(mut self, _dep: u32) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = handle_label(&self); spawn(async move { let choices = ["read a chunk".to_string(), "drop the stream".to_string()]; loop { @@ -520,15 +551,7 @@ where T: Dialog + ToValue + ValueTyped + wit_future::FuturePayload, { fn read_value(_dep: u32) -> Self { - let (writer, reader) = wit_future::new::< - T, - >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::dialog::print( - 0, - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (writer, reader, label) = host_future::(); spawn(async move { proxy::util::dialog::print(0, &format!("The host writes {label}")); let value: T = Dialog::read_value(1); @@ -542,7 +565,7 @@ where reader } fn consume(self, _dep: u32) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = handle_label(&self); spawn(async move { let choices = ["read the value".to_string(), "drop the future".to_string()]; let prompt = format!("The host reads {label}"); @@ -567,8 +590,13 @@ impl Dialog for wit_bindgen::rt::async_support::ErrorContext { } } thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { - std::cell::Cell::new(false) }; + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() } type Job = std::pin::Pin>>; #[derive(Default)] @@ -579,7 +607,59 @@ struct Executor { } thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = + const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. A job counts them, waking the executor itself in between. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + #[derive(Default)] + struct Done { + step: Option, + waker: Option, + } + let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_done = done.clone(); + let mut left = runs; + spawn( + std::future::poll_fn(move |_| { + if left == 0 { + let mut done = job_done.borrow_mut(); + done.step = Some(current_step()); + if let Some(waker) = done.waker.take() { + waker.wake(); + } + return std::task::Poll::Ready(()); + } + left -= 1; + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }), + ); + std::future::poll_fn(move |cx| { + let mut done = done.borrow_mut(); + match done.step { + Some(step) => std::task::Poll::Ready(step), + None => { + done.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -597,6 +677,8 @@ fn spawn(job: impl std::future::Future + 'static) { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + next_step(); + WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); loop { let mut queued = EXECUTOR @@ -613,6 +695,9 @@ async fn run_executor() { std::task::Poll::Ready(()) } else { e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + cx.waker().wake_by_ref(); + } std::task::Poll::Pending } }) diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 951ac66..cc2f3f4 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -5,6 +5,30 @@ bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + /// Calls an async export, and records that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export( + id: TaskId, + call: impl std::future::Future, + ) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await + } let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -15,13 +39,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let url: String = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/api@0.1.0.handle", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/api@0.1.0.handle", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::api::handle(url)).await; + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::p3::api::handle(url).await; + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -31,13 +74,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let port: u16 = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&port)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/api@0.1.0.serve", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/api@0.1.0.serve", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::api::serve(port)).await; + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - Fuzz::consume(test::p3::api::serve(port).await, &mut u); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -49,13 +111,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ) .unwrap(); __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/api@0.1.0.echo", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/api@0.1.0.echo", + false, + __params, + ) + }, + ); + let res = test::p3::api::echo(s); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - Fuzz::consume(test::p3::api::echo(s), &mut u); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -65,13 +146,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let url: String = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/host@0.1.0.fetch", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.fetch", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::host::fetch(url)).await; + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::p3::host::fetch(url).await; + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -81,13 +181,28 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let ms: u64 = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/host@0.1.0.sleep", __params - .join(", ") - ), + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.sleep", + true, + __params, + ) + }, ); - let _ = test::p3::host::sleep(ms).await; + let res = call_export(__id, test::p3::host::sleep(ms)).await; + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, None), + ); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -100,13 +215,28 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&err)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/host@0.1.0.report", __params - .join(", ") - ), + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.report", + false, + __params, + ) + }, ); - let _ = test::p3::host::report(err); + let res = test::p3::host::report(err); + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, None), + ); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -118,13 +248,28 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { > = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/host@0.1.0.finish", __params - .join(", ") - ), + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.finish", + false, + __params, + ) + }, ); - let _ = test::p3::host::finish(done); + let res = test::p3::host::finish(done); + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, None), + ); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -138,13 +283,32 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .push( wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap(), ); - proxy::util::debug::print( - &format!( - "export: {}({})", "new test:p3/host/body@0.1.0", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "new test:p3/host/body@0.1.0", + false, + __params, + ) + }, + ); + let res = test::p3::host::Body::new(contents); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - let _ = test::p3::host::Body::new(contents); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } @@ -154,23 +318,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let this: test::p3::host::Body = Fuzz::fuzz(&mut u).unwrap(); __params .push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); - proxy::util::debug::print( - &format!( - "export: {}({})", "test:p3/host/body@0.1.0.consume", __params - .join(", ") + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host/body@0.1.0.consume", + false, + __params, + ) + }, + ); + let res = test::p3::host::Body::consume(this); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), ), ); - Fuzz::consume(test::p3::host::Body::consume(this), &mut u); + proxy::conversion::executor::host_drops(true); + Fuzz::consume(res, &mut u); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); for _ in 0..u.int_in_range(0..=3u8).unwrap() { wit_bindgen::yield_async().await; } } _ => unreachable!(), } - SCOPED_ALLOC - .with(|alloc| { - alloc.borrow_mut().clear(); - }); } join_spawned().await; proxy::conversion::executor::stop(); @@ -310,6 +489,13 @@ impl Fuzz for test::p3::host::ErrorCode { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + test::p3::host::ErrorCode::Timeout => {} + test::p3::host::ErrorCode::Other(x) => Fuzz::consume(x, u), + } + } } impl Fuzz for test::p3::host::Body { fn fuzz(_u: &mut Unstructured<'_>) -> Result { @@ -340,7 +526,7 @@ trait Fuzz: Sized { impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), }) } @@ -406,33 +592,114 @@ impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +thread_local! { + static READER_LABELS : std::cell::RefCell < std::collections::HashMap < u32, u32 >> = + std::cell::RefCell::new(std::collections::HashMap::new()); +} +/// Labels a stream or future this component made by the handle of its `writer`, which it +/// keeps for the stream's lifetime, also where its `reader` is printed: in the value that +/// hands the reader out, after which the reader moves on, and its handle can be reused. +/// So the entry is gone once the reader is printed. +fn label_by_writer(reader: u32, writer: u32) { + static HOOK: std::sync::Once = std::sync::Once::new(); + HOOK.call_once(|| { + wasm_wave::value::convert::set_handle_label(|_kind, handle| { + READER_LABELS.with_borrow_mut(|labels| labels.remove(&handle)) + }) + }); + READER_LABELS + .with_borrow_mut(|labels| { + labels.remove(&writer); + labels.insert(reader, writer); + }); +} +/// The label of a stream or future, e.g. `#stream-7`, by the end this component keeps. +fn handle_label(end: &impl ToValue) -> String { + wasm_wave::to_string(&end.to_value()).unwrap() +} +/// A stream the host writes: its writer, its reader, to hand out, and its label. +fn host_stream() -> ( + wit_bindgen::StreamWriter, + wit_bindgen::StreamReader, + String, +) { + let (writer, reader) = wit_stream::new::(); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} +/// A future the host writes, which it always does: its writer, its reader, to hand +/// out, and its label. +fn host_future() -> ( + wit_bindgen::FutureWriter, + wit_bindgen::FutureReader, + String, +) { + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} +/// The label of a stream or future, by the end this component keeps. +fn label_of(end: &impl ToValue) -> Label { + Label { + table: TRACE_TABLE, + name: handle_label(end), + } +} +/// A host line, in step `at`, in which its job made it, e.g. issued the write the +/// guest read; or, by default, in the current one. +fn host_line_at(at: u32, event: Event) -> Line { + Line { + by: Party::Host, + at: Some(at), + event, + } +} +fn host_line(event: Event) -> Line { + host_line_at(trace_at(), event) +} +fn guest_line(event: Event) -> Line { + Line { + by: Party::Guest, + at: None, + event, + } +} impl Fuzz for wit_bindgen::StreamReader where T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, { fn fuzz(u: &mut Unstructured<'_>) -> Result { - let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::debug::print( - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (mut writer, reader, name) = host_stream::(); + let label = Label { table: TRACE_TABLE, name }; let chunks: Vec> = Fuzz::fuzz(u)?; spawn(async move { - for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { + let mut chunks = chunks.into_iter().filter(|chunk| !chunk.is_empty()); + while let Some(chunk) = chunks.next() { let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); - if !writer.write_all(chunk).await.is_empty() { - proxy::util::debug::print(&format!("{label} dropped by guest")); + let at = trace_at(); + let rest = writer.write_all(chunk).await; + if !rest.is_empty() { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((rest, chunks.collect::>()))); return; } - proxy::util::debug::print(&format!("{label}.read() = {items}")); + let chunk = Chunk { + stream: label.clone(), + items, + }; + proxy::recorder::record::record(&host_line_at(at, Event::Write(chunk))); } - proxy::util::debug::print(&format!("{label}.read() = dropped")); + drop(writer); + proxy::recorder::record::record(&host_line(Event::Drop(label))); }); Ok(reader) } fn consume(mut self, u: &mut Unstructured<'_>) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = label_of(&self); let reads: Option = match u.int_in_range(0..=9u8).unwrap_or(0) { 8 => Some(u.int_in_range(1..=4).unwrap_or(1)), 9 => Some(0), @@ -442,16 +709,23 @@ where spawn(async move { let mut count = 0; while reads != Some(count) { + let at = trace_at(); let (result, items) = self.read(Vec::with_capacity(capacity)).await; if let wit_bindgen::StreamResult::Dropped = result { - proxy::util::debug::print(&format!("{label} -> dropped")); + proxy::recorder::record::record(&guest_line(Event::Drop(label))); return; } - let items = wasm_wave::to_string(&items.to_value()).unwrap(); - proxy::util::debug::print(&format!("{label} -> {items}")); + let wave = wasm_wave::to_string(&items.to_value()).unwrap(); + let chunk = Chunk { + stream: label.clone(), + items: wave, + }; + proxy::recorder::record::record(&host_line_at(at, Event::Read(chunk))); + host_dropping(move || drop(items)); count += 1; } - proxy::util::debug::print(&format!("{label} dropped by host")); + drop(self); + proxy::recorder::record::record(&host_line(Event::Drop(label))); }); } } @@ -461,34 +735,47 @@ where { fn fuzz(u: &mut Unstructured<'_>) -> Result { let value = T::fuzz(u)?; - let (writer, reader) = wit_future::new::< - T, - >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::debug::print( - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (writer, reader, name) = host_future::(); + let label = Label { table: TRACE_TABLE, name }; let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { - if writer.write(value).await.is_ok() { - proxy::util::debug::print(&format!("{label}.read() = {wave}")); - } else { - proxy::util::debug::print(&format!("{label} dropped by guest")); + let at = trace_at(); + match writer.write(value).await { + Ok(()) => { + let value = FutureValue { + future: label, + value: wave, + }; + proxy::recorder::record::record( + &host_line_at(at, Event::Value(value)), + ); + } + Err(unwritten) => { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); + } } }); Ok(reader) } fn consume(self, u: &mut Unstructured<'_>) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = label_of(&self); let read = u.int_in_range(0..=9u8).unwrap_or(0) != 9; spawn(async move { if read { - let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); - proxy::util::debug::print(&format!("{label} -> {value}")); + let value = self.await; + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + let line = guest_line( + Event::Value(FutureValue { + future: label, + value: wave, + }), + ); + proxy::recorder::record::record(&line); + host_dropping(move || drop(value)); } else { drop(self); - proxy::util::debug::print(&format!("{label} dropped by host")); + proxy::recorder::record::record(&host_line(Event::Drop(label))); } }); } @@ -498,10 +785,96 @@ impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { Ok(Self::new(&String::fuzz(u)?)) } } +use bindings::proxy::recorder::types::*; thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Exports; +#[allow(dead_code)] +fn trace_at() -> u32 { + proxy::conversion::executor::step() +} +#[allow(dead_code)] +fn host_dropping(f: impl FnOnce() -> R) -> R { + proxy::conversion::executor::host_drops(true); + let res = f(); + proxy::conversion::executor::host_drops(false); + res +} +thread_local! { + #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } +thread_local! { + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; + static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { + std::cell::Cell::new(None) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() +} thread_local! { static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const { std::cell::RefCell::new(Vec::new()) }; diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 6f6bbe4..269dd38 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -8,7 +8,7 @@ impl exports::proxy::conversion::conversion::Guest for Stub { ) -> exports::proxy::conversion::conversion::TestP3HostBody { let _ = handle; exports::proxy::conversion::conversion::TestP3HostBody::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "body".to_string(), }) } @@ -25,9 +25,17 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn stop() -> () { stop_executor() } + fn step() -> u32 { + next_step() + } + fn host_drops(dropping: bool) -> () { + MOVING.with(|moving| moving.set(dropping)); + HOST_DROP_AT.set(dropping.then(next_step)); + } } impl exports::test::p3::host::Guest for Stub { type Body = MockedResource; + #[allow(unused_variables)] async fn fetch( url: String, ) -> ::core::result::Result< @@ -36,35 +44,110 @@ impl exports::test::p3::host::Guest for Stub { > { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "test:p3/host@0.1.0.fetch", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:p3/host@0.1.0.fetch", + true, + __params.clone(), + ), ); let __random = proxy::util::debug::get_random(); let mut __host = Unstructured::new(&__random); - for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { - wit_bindgen::yield_async().await; - } + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __runs = __host.int_in_range(0..=3u8).unwrap_or(0); + let __at = if __runs > 0 { + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Started(__id), + }, + ); + Some(wait_for_runs(__runs as u32).await) + } else { + None + }; let __buf = __params.join(",") + "test:p3/host@0.1.0.fetch"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } + #[allow(unused_variables)] async fn sleep(ms: u64) -> () { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "test:p3/host@0.1.0.sleep", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:p3/host@0.1.0.sleep", + true, + __params.clone(), + ), ); let __random = proxy::util::debug::get_random(); let mut __host = Unstructured::new(&__random); - for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { - wit_bindgen::yield_async().await; - } + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __runs = __host.int_in_range(0..=3u8).unwrap_or(0); + let __at = if __runs > 0 { + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Started(__id), + }, + ); + Some(wait_for_runs(__runs as u32).await) + } else { + None + }; + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, + ); + } + #[allow(unused_variables)] + fn report(err: wit_bindgen::rt::async_support::ErrorContext) -> () { + let mut __params: Vec = Vec::new(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&err)).unwrap()); + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:p3/host@0.1.0.report", + false, + __params.clone(), + ), + ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, + ); } #[allow(unused_variables)] - fn report(err: wit_bindgen::rt::async_support::ErrorContext) -> () {} fn finish( done: wit_bindgen::rt::async_support::FutureReader< ::core::result::Result<(), exports::test::p3::host::ErrorCode>, @@ -72,15 +155,32 @@ impl exports::test::p3::host::Guest for Stub { ) -> () { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "test:p3/host@0.1.0.finish", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:p3/host@0.1.0.finish", + false, + __params.clone(), + ), ); let __random = proxy::util::debug::get_random(); let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); Fuzz::consume(done, &mut __host); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, + ); } } impl exports::test::p3::host::GuestBody for MockedResource { + #[allow(unused_variables)] fn new( contents: ::core::option::Option< wit_bindgen::rt::async_support::StreamReader, @@ -88,42 +188,82 @@ impl exports::test::p3::host::GuestBody for MockedResource { ) -> Self { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap()); - proxy::util::debug::print( - &format!("import: {}({})", "new test:p3/host/body@0.1.0", __params.join(",")), + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "new test:p3/host/body@0.1.0", + false, + __params.clone(), + ), ); let __random = proxy::util::debug::get_random(); let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); Fuzz::consume(contents, &mut __host); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "new test:p3/host/body@0.1.0"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = MockedResource { name: "body".to_string(), ..Fuzz::fuzz(&mut u).unwrap() }; - PENDING_RET.with(|pending| pending.set(true)); + let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } + #[allow(unused_variables)] async fn size(&self) -> u64 { let mut __params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; - proxy::util::debug::print( - &format!( - "import: {}({})", "test:p3/host/body@0.1.0.size", __params.join(",") + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:p3/host/body@0.1.0.size", + true, + __params.clone(), ), ); let __random = proxy::util::debug::get_random(); let mut __host = Unstructured::new(&__random); - for _ in 0..__host.int_in_range(0..=3u8).unwrap_or(0) { - wit_bindgen::yield_async().await; - } + MOVING.with(|moving| moving.set(true)); + MOVING.with(|moving| moving.set(false)); + let __runs = __host.int_in_range(0..=3u8).unwrap_or(0); + let __at = if __runs > 0 { + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Started(__id), + }, + ); + Some(wait_for_runs(__runs as u32).await) + } else { + None + }; let __buf = __params.join(",") + "test:p3/host/body@0.1.0.size"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } + #[allow(unused_variables)] fn consume( this: exports::test::p3::host::Body, ) -> ( @@ -137,41 +277,48 @@ impl exports::test::p3::host::GuestBody for MockedResource { ) { let mut __params: Vec = Vec::new(); __params.push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); - proxy::util::debug::print( - &format!( - "import: {}({})", "test:p3/host/body@0.1.0.consume", __params.join(",") + let __id = trace_task_id(Table::Imports); + proxy::recorder::record::record( + &trace_call( + Party::Guest, + __id, + "test:p3/host/body@0.1.0.consume", + false, + __params.clone(), ), ); + let __random = proxy::util::debug::get_random(); + let mut __host = Unstructured::new(&__random); + MOVING.with(|moving| moving.set(true)); + drop((this,)); + MOVING.with(|moving| moving.set(false)); + let __at: Option = None; let __buf = __params.join(",") + "test:p3/host/body@0.1.0.consume"; let mut u = Unstructured::new(&__buf.as_bytes()); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); - proxy::util::debug::print(&format!("ret: {}", res_str)); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(res_str)) + }, + ); res } - unsafe fn _resource_new(val: *mut u8) -> u32 { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "[export]test:p3/host@0.1.0")] - unsafe extern "C" { - #[link_name = "[resource-new]body"] - fn new(_: *mut u8) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new(_: *mut u8) -> i32 { - unreachable!() - } - let handle = unsafe { new(val) } as u32; - let mock = unsafe { &mut *(val as *mut Option) } - .as_mut() - .unwrap(); - mock.handle = handle; - if PENDING_RET.with(|pending| pending.take()) { - let ret = format!( - "ret: {}", wasm_wave::to_string(& mock.to_value()).unwrap() - ); - proxy::util::debug::print(&ret); + unsafe fn resource_from_raw_(handle: *mut Option) -> Option { + if let Some(res) = *unsafe { Box::from_raw(handle) } { + let name = wasm_wave::to_string(&res.to_value()).unwrap(); + let line = if MOVING.with(|moving| moving.get()) { + Line { + at: HOST_DROP_AT.get(), + ..trace_drop(Party::Host, Table::Imports, name) + } + } else { + trace_drop(Party::Guest, Table::Imports, name) + }; + proxy::recorder::record::record(&line); } - handle + None } } impl ValueTyped for exports::test::p3::host::ErrorCode { @@ -303,12 +450,19 @@ impl Fuzz for exports::test::p3::host::ErrorCode { _ => unreachable!(), } } + #[allow(unused_variables)] + fn consume(self, u: &mut Unstructured<'_>) { + match self { + exports::test::p3::host::ErrorCode::Timeout => {} + exports::test::p3::host::ErrorCode::Other(x) => Fuzz::consume(x, u), + } + } } impl Fuzz for exports::test::p3::host::Body { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok( exports::test::p3::host::Body::new(MockedResource { - handle: 0, + handle: next_mock_label(), name: "body".to_string(), }), ) @@ -329,7 +483,7 @@ trait Fuzz: Sized { impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { - handle: 0, + handle: next_mock_label(), name: "mocked-resource".to_string(), }) } @@ -395,33 +549,114 @@ impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6); impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7); impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); impl_fuzz_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +thread_local! { + static READER_LABELS : std::cell::RefCell < std::collections::HashMap < u32, u32 >> = + std::cell::RefCell::new(std::collections::HashMap::new()); +} +/// Labels a stream or future this component made by the handle of its `writer`, which it +/// keeps for the stream's lifetime, also where its `reader` is printed: in the value that +/// hands the reader out, after which the reader moves on, and its handle can be reused. +/// So the entry is gone once the reader is printed. +fn label_by_writer(reader: u32, writer: u32) { + static HOOK: std::sync::Once = std::sync::Once::new(); + HOOK.call_once(|| { + wasm_wave::value::convert::set_handle_label(|_kind, handle| { + READER_LABELS.with_borrow_mut(|labels| labels.remove(&handle)) + }) + }); + READER_LABELS + .with_borrow_mut(|labels| { + labels.remove(&writer); + labels.insert(reader, writer); + }); +} +/// The label of a stream or future, e.g. `#stream-7`, by the end this component keeps. +fn handle_label(end: &impl ToValue) -> String { + wasm_wave::to_string(&end.to_value()).unwrap() +} +/// A stream the host writes: its writer, its reader, to hand out, and its label. +fn host_stream() -> ( + wit_bindgen::StreamWriter, + wit_bindgen::StreamReader, + String, +) { + let (writer, reader) = wit_stream::new::(); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} +/// A future the host writes, which it always does: its writer, its reader, to hand +/// out, and its label. +fn host_future() -> ( + wit_bindgen::FutureWriter, + wit_bindgen::FutureReader, + String, +) { + let (writer, reader) = wit_future::new::< + T, + >(|| unreachable!("the value is always written")); + label_by_writer(reader.handle(), writer.handle()); + let label = handle_label(&writer); + (writer, reader, label) +} +/// The label of a stream or future, by the end this component keeps. +fn label_of(end: &impl ToValue) -> Label { + Label { + table: TRACE_TABLE, + name: handle_label(end), + } +} +/// A host line, in step `at`, in which its job made it, e.g. issued the write the +/// guest read; or, by default, in the current one. +fn host_line_at(at: u32, event: Event) -> Line { + Line { + by: Party::Host, + at: Some(at), + event, + } +} +fn host_line(event: Event) -> Line { + host_line_at(trace_at(), event) +} +fn guest_line(event: Event) -> Line { + Line { + by: Party::Guest, + at: None, + event, + } +} impl Fuzz for wit_bindgen::StreamReader where T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, { fn fuzz(u: &mut Unstructured<'_>) -> Result { - let (mut writer, reader) = wit_stream::new::(); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::debug::print( - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (mut writer, reader, name) = host_stream::(); + let label = Label { table: TRACE_TABLE, name }; let chunks: Vec> = Fuzz::fuzz(u)?; spawn(async move { - for chunk in chunks.into_iter().filter(|chunk| !chunk.is_empty()) { + let mut chunks = chunks.into_iter().filter(|chunk| !chunk.is_empty()); + while let Some(chunk) = chunks.next() { let items = wasm_wave::to_string(&chunk.to_value()).unwrap(); - if !writer.write_all(chunk).await.is_empty() { - proxy::util::debug::print(&format!("{label} dropped by guest")); + let at = trace_at(); + let rest = writer.write_all(chunk).await; + if !rest.is_empty() { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((rest, chunks.collect::>()))); return; } - proxy::util::debug::print(&format!("{label}.read() = {items}")); + let chunk = Chunk { + stream: label.clone(), + items, + }; + proxy::recorder::record::record(&host_line_at(at, Event::Write(chunk))); } - proxy::util::debug::print(&format!("{label}.read() = dropped")); + drop(writer); + proxy::recorder::record::record(&host_line(Event::Drop(label))); }); Ok(reader) } fn consume(mut self, u: &mut Unstructured<'_>) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = label_of(&self); let reads: Option = match u.int_in_range(0..=9u8).unwrap_or(0) { 8 => Some(u.int_in_range(1..=4).unwrap_or(1)), 9 => Some(0), @@ -431,16 +666,23 @@ where spawn(async move { let mut count = 0; while reads != Some(count) { + let at = trace_at(); let (result, items) = self.read(Vec::with_capacity(capacity)).await; if let wit_bindgen::StreamResult::Dropped = result { - proxy::util::debug::print(&format!("{label} -> dropped")); + proxy::recorder::record::record(&guest_line(Event::Drop(label))); return; } - let items = wasm_wave::to_string(&items.to_value()).unwrap(); - proxy::util::debug::print(&format!("{label} -> {items}")); + let wave = wasm_wave::to_string(&items.to_value()).unwrap(); + let chunk = Chunk { + stream: label.clone(), + items: wave, + }; + proxy::recorder::record::record(&host_line_at(at, Event::Read(chunk))); + host_dropping(move || drop(items)); count += 1; } - proxy::util::debug::print(&format!("{label} dropped by host")); + drop(self); + proxy::recorder::record::record(&host_line(Event::Drop(label))); }); } } @@ -450,34 +692,47 @@ where { fn fuzz(u: &mut Unstructured<'_>) -> Result { let value = T::fuzz(u)?; - let (writer, reader) = wit_future::new::< - T, - >(|| unreachable!("the value is always written")); - let label = wasm_wave::to_string(&ToValue::to_value(&writer)).unwrap(); - let reader_label = wasm_wave::to_string(&ToValue::to_value(&reader)).unwrap(); - proxy::util::debug::print( - &format!("new (writer: {label}, reader: {reader_label})"), - ); + let (writer, reader, name) = host_future::(); + let label = Label { table: TRACE_TABLE, name }; let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { - if writer.write(value).await.is_ok() { - proxy::util::debug::print(&format!("{label}.read() = {wave}")); - } else { - proxy::util::debug::print(&format!("{label} dropped by guest")); + let at = trace_at(); + match writer.write(value).await { + Ok(()) => { + let value = FutureValue { + future: label, + value: wave, + }; + proxy::recorder::record::record( + &host_line_at(at, Event::Value(value)), + ); + } + Err(unwritten) => { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); + } } }); Ok(reader) } fn consume(self, u: &mut Unstructured<'_>) { - let label = wasm_wave::to_string(&ToValue::to_value(&self)).unwrap(); + let label = label_of(&self); let read = u.int_in_range(0..=9u8).unwrap_or(0) != 9; spawn(async move { if read { - let value = wasm_wave::to_string(&self.await.to_value()).unwrap(); - proxy::util::debug::print(&format!("{label} -> {value}")); + let value = self.await; + let wave = wasm_wave::to_string(&value.to_value()).unwrap(); + let line = guest_line( + Event::Value(FutureValue { + future: label, + value: wave, + }), + ); + proxy::recorder::record::record(&line); + host_dropping(move || drop(value)); } else { drop(self); - proxy::util::debug::print(&format!("{label} dropped by host")); + proxy::recorder::record::record(&host_line(Event::Drop(label))); } }); } @@ -487,10 +742,98 @@ impl Fuzz for wit_bindgen::rt::async_support::ErrorContext { Ok(Self::new(&String::fuzz(u)?)) } } +use bindings::proxy::recorder::types::*; thread_local! { - #[allow(dead_code)] static PENDING_RET : std::cell::Cell < bool > = const { + static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +#[allow(dead_code)] +fn trace_task_id(table: Table) -> TaskId { + let id = TASK_ID + .with(|id| { + id.set(id.get() + 1); + id.get() + }); + TaskId { table, id } +} +#[allow(dead_code)] +fn trace_call( + by: Party, + id: TaskId, + func: &str, + is_async: bool, + args: Vec, +) -> Line { + let call = Call { + id, + is_async, + func: func.to_string(), + args, + }; + Line { + by, + at: None, + event: Event::Call(call), + } +} +#[allow(dead_code)] +fn trace_return(by: Party, id: TaskId, value: Option) -> Line { + let ret = ReturnValue { id, value }; + Line { + by, + at: None, + event: Event::Return(ret), + } +} +#[allow(dead_code)] +fn trace_drop(by: Party, table: Table, name: String) -> Line { + let label = Label { table, name }; + Line { + by, + at: None, + event: Event::Drop(label), + } +} +/// The value of the `return` among the host's answers to a call. +#[allow(dead_code)] +fn trace_return_value(answers: Vec) -> Option { + answers + .into_iter() + .find_map(|line| match line.event { + Event::Return(ret) => Some(ret.value), + _ => None, + }) + .expect("the trace doesn't answer the call") +} +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Imports; +#[allow(dead_code)] +fn trace_at() -> u32 { + current_step() +} +#[allow(dead_code)] +fn host_dropping(f: impl FnOnce() -> R) -> R { + MOVING.with(|moving| moving.set(true)); + HOST_DROP_AT.set(Some(current_step())); + let res = f(); + MOVING.with(|moving| moving.set(false)); + HOST_DROP_AT.set(None); + res +} +thread_local! { + #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } +thread_local! { + static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; + static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { + std::cell::Cell::new(None) }; +} +/// The label of a new mock. +#[allow(dead_code)] +fn next_mock_label() -> u32 { + MOCK_LABEL.set(MOCK_LABEL.get() + 1); + MOCK_LABEL.get() +} type Job = std::pin::Pin>>; #[derive(Default)] struct Executor { @@ -500,7 +843,59 @@ struct Executor { } thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = + const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. A job counts them, waking the executor itself in between. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + #[derive(Default)] + struct Done { + step: Option, + waker: Option, + } + let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_done = done.clone(); + let mut left = runs; + spawn( + std::future::poll_fn(move |_| { + if left == 0 { + let mut done = job_done.borrow_mut(); + done.step = Some(current_step()); + if let Some(waker) = done.waker.take() { + waker.wake(); + } + return std::task::Poll::Ready(()); + } + left -= 1; + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }), + ); + std::future::poll_fn(move |cx| { + let mut done = done.borrow_mut(); + match done.step { + Some(step) => std::task::Poll::Ready(step), + None => { + done.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -518,6 +913,8 @@ fn spawn(job: impl std::future::Future + 'static) { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + next_step(); + WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); loop { let mut queued = EXECUTOR @@ -534,6 +931,9 @@ async fn run_executor() { std::task::Poll::Ready(()) } else { e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + cx.waker().wake_by_ref(); + } std::task::Poll::Pending } }) diff --git a/tests/snapshots/p3-fuzz/wit/component.wit b/tests/snapshots/p3-fuzz/wit/component.wit index 558e68b..1b06df7 100644 --- a/tests/snapshots/p3-fuzz/wit/component.wit +++ b/tests/snapshots/p3-fuzz/wit/component.wit @@ -1,6 +1,7 @@ package component:proxy; world imports { import proxy:util/debug; + import proxy:recorder/%record@0.1.0; export proxy:conversion/conversion; export proxy:conversion/executor; export test:p3/host@0.1.0; @@ -11,6 +12,7 @@ world tmp-exports { } world exports { import proxy:util/debug; + import proxy:recorder/%record@0.1.0; import proxy:conversion/conversion; import proxy:conversion/executor; import test:p3/host@0.1.0; diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index f72ffce..a9fd952 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -586,6 +586,12 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Exports; +#[allow(dead_code)] +fn trace_at() -> u32 { + proxy::conversion::executor::step() +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index 28a498d..5fbb9e0 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -19,6 +19,12 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn stop() -> () { stop_executor() } + fn step() -> u32 { + next_step() + } + fn host_drops(dropping: bool) -> () { + MOVING.with(|moving| moving.set(dropping)) + } } impl exports::test::host::types::Guest for Stub { type Counter = MockedResource; @@ -556,6 +562,12 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Imports; +#[allow(dead_code)] +fn trace_at() -> u32 { + current_step() +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; @@ -569,7 +581,59 @@ struct Executor { } thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = - std::cell::RefCell::new(Executor::default()); + std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = + const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. A job counts them, waking the executor itself in between. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + #[derive(Default)] + struct Done { + step: Option, + waker: Option, + } + let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_done = done.clone(); + let mut left = runs; + spawn( + std::future::poll_fn(move |_| { + if left == 0 { + let mut done = job_done.borrow_mut(); + done.step = Some(current_step()); + if let Some(waker) = done.waker.take() { + waker.wake(); + } + return std::task::Poll::Ready(()); + } + left -= 1; + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }), + ); + std::future::poll_fn(move |cx| { + let mut done = done.borrow_mut(); + match done.step { + Some(step) => std::task::Poll::Ready(step), + None => { + done.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -587,6 +651,8 @@ fn spawn(job: impl std::future::Future + 'static) { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + next_step(); + WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); loop { let mut queued = EXECUTOR @@ -603,6 +669,9 @@ async fn run_executor() { std::task::Poll::Ready(()) } else { e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + cx.waker().wake_by_ref(); + } std::task::Poll::Pending } }) From bff7ab24b7b18ef1c3ebf2a644dcc9dcc3572fcc Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Wed, 4 Nov 2026 19:58:46 -0800 Subject: [PATCH 13/25] Replay works except label and ToRust - Recorder interface: take-host-line(key), keyed by a task or a label, and finish. next-host-line now returns only what start performs, export calls and the host's drops of the guest's resources, and skips guest lines that may still come. Both recorders implement the new functions. - Executor: wait_until_step, with an async wait-until-step export for start. It panics if replay passes the step, which means replay diverged. - Replay codegen: - Async import mocks: a call recorded RETURNED returns at once. A STARTED one takes its Return and waits for that line's step. - start: each line it performs waits for the step before its own, takes its step and checks the number. call_export checks the guest's Started. - Arguments: borrowed arguments are dropped as the host's drops. - finish runs at the end. - Mock labels: replay now labels mocks by their trace labels (mock_labels, get-handle-mock-*). --- assets/executor.rs | 51 +- assets/executor.wit | 6 +- assets/recorder.one_byte_page.wasm | Bin 303659 -> 308856 bytes assets/recorder.wasm | Bin 303663 -> 308860 bytes assets/recorder.wit | 15 +- components/recorder/src/lib.rs | 10 +- crates/trace/src/lib.rs | 88 ++- src/ast.rs | 12 +- src/codegen/fuzz.rs | 36 +- src/codegen/mock.rs | 17 +- src/codegen/mod.rs | 40 +- src/codegen/replay.rs | 63 ++- src/run.rs | 9 + src/traits/mod.rs | 3 +- tests/snapshots/dialog/record_imports/lib.rs | 56 +- tests/snapshots/fuzz/record_exports/lib.rs | 517 ++++++++++-------- tests/snapshots/fuzz/record_imports/lib.rs | 73 ++- .../snapshots/p3-dialog/record_imports/lib.rs | 56 +- tests/snapshots/p3-fuzz/record_exports/lib.rs | 512 +++++++++-------- tests/snapshots/p3-fuzz/record_imports/lib.rs | 73 ++- tests/snapshots/replay/record_exports/lib.rs | 263 ++++++--- tests/snapshots/replay/record_imports/lib.rs | 76 ++- .../replay/wit/deps/conversion/conversion.wit | 1 + 23 files changed, 1238 insertions(+), 739 deletions(-) diff --git a/assets/executor.rs b/assets/executor.rs index fbe322a..53bfb8d 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -74,36 +74,63 @@ fn current_step() -> u32 { STEP.get() } /// Waits, from any task, until the executor has run `runs` more times, and returns the step of -/// the last run. A job counts them, waking the executor itself in between. +/// the last run. #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is +/// already there, e.g. replay's for a host line recorded in that step. +/// +/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() == step { + return; + } + wait_for_run(move || { + let now = current_step(); + assert!(now <= step, "replay reached step {now} before the run in step {step}"); + now == step + }) + .await; +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, } - let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_done = done.clone(); - let mut left = runs; + let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_shared = shared.clone(); spawn(std::future::poll_fn(move |_| { - if left == 0 { - let mut done = job_done.borrow_mut(); - done.step = Some(current_step()); - if let Some(waker) = done.waker.take() { + if done() { + let mut shared = job_shared.borrow_mut(); + shared.step = Some(current_step()); + if let Some(waker) = shared.waker.take() { waker.wake(); } return std::task::Poll::Ready(()); } - left -= 1; WAKE_AGAIN.set(true); std::task::Poll::Pending })); std::future::poll_fn(move |cx| { - let mut done = done.borrow_mut(); - match done.step { + let mut shared = shared.borrow_mut(); + match shared.step { Some(step) => std::task::Poll::Ready(step), None => { - done.waker = Some(cx.waker().clone()); + shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending } } diff --git a/assets/executor.wit b/assets/executor.wit index 7848ac5..80c3ec4 100644 --- a/assets/executor.wit +++ b/assets/executor.wit @@ -11,8 +11,10 @@ interface executor { // Counts a step that isn't a run of the executor, e.g. the arrival of an export call, which the // exports component makes, and returns it (see `docs/p3-record-replay.md`, Steps). step: func() -> u32; + // Waits until the executor's run in `step`, or returns at once if the count is already there: + // replay's `start` waits for the step before an export call's. + wait-until-step: async func(step: u32); // Whether the exports component is dropping mocks of the imports component itself, e.g. those - // it made for the borrowed arguments of an export call: the host's drops, not the guest's, - // which take a step, as an export call does. + // it made for the borrowed arguments of an export call: the host's drops, not the guest's. host-drops: func(dropping: bool); } diff --git a/assets/recorder.one_byte_page.wasm b/assets/recorder.one_byte_page.wasm index e704d83ae88422036a5758141478cdbc3c6308d7..a6cfd65fe251488e05af6d221b661d73c9760697 100644 GIT binary patch delta 42030 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 N>|;yv;33{_`Zvhbn27)Y delta 37110 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 diff --git a/assets/recorder.wasm b/assets/recorder.wasm index 9c8ab956c7eecf28a560f9abe9a414f449928427..1312fcced783c00f14ee2c39706c4533fab31fc8 100644 GIT binary patch delta 42231 zcmd3P2YggT*Y}>eH+54s5J&}fLklGkAS4i^Z0NmNK+zD2Lg-D!me6~>!hm!ILN6jL zib_>ck)k|^1q2%kNKsT&l<$A$-rZ~xc=UPR?|XkA{Bq|`KXclgIWu#2>!DJ&E|-e1 z<%}8Sh2kQz@L#FYh3$$U=^Ck(-cc!HJ9Q1Q=h@sb{D|d8 zJU?da+-Fmv#|+o)HA7V*iE}FQqY6Kk?lJS0T4j$LRUB)yzeK2U)Zw8d!x5Dj^60>! z14j+8RjiI!4N-}MM91iyLH$z(3>!5%h0$cIELSbks^%3-b%yoL=`%2UWdEGOgNOCC z{UJhT39`A#K9|b(E0>tCQ~W`F_fy9{ox64EIxu(8;2}eY4Va`qTA**I$NIPLetKb#6|I&I4wRD z$HfUTM;sGZ#1G=8I491Cv*HW!yErOliVNZ^@wNC?d?&sUd&T$SOYx4lEItue#VOJB zr1(g@EpCgA+9qwYwnf{jZPPx`KG9BVpJ^X!bM-m;_u5o_nm$usrvIod(%0#s6F#yV zy0F=IXe5lTUE++JrCQTIqgm+~`poE2x6R_C|4xw^4Cb@mvz;kF83KE!Bk=TgXSLt8t92 zJCr3gR134s;*(q!&fX59z`TmURld!A+%_o5UP-+>ei|pE7K{ywQM0CuUJW9lknEe;-u*E z4aL7}eNTCAJ*EP;KDYw6zGIFvo}yHvPCK)!-5s0j_`-P2l?{fDPl^Xa*H3cRG8x+L zK5XM6*KCC>jqZE<0O@A|G9jrmv-HWNJlb#ETd_X(6?lQY7x&Iqhr znSL~CRvuND8NjVL!;!hY@}t0cY?Wj@-Bo&(RrTHEiUnYwp0r$*7<>IW1v17RL?9sd!oWh#c_(y z4i_BA2kB-QQ>&-Y%f_ziEf}wb*_i7% z$EPHf;_&LZN~T;f_;za)Mzxw@&4M$8?u6u36MzEBkl$i6M6|ciO*Paca_hN{_qaBS zP_n>8mYXgBmEnwzZ#KikjuY`}T55(CZ1#X!mkRa)Q|=mo4B(Obv}z77-h@oH47 zQPC2(ipGcbMI5>DC0ls;jDQ8OR^YkX_Im_ z+ljaGcMPn#i6BU2YaJ~V5^{#Q$r%dn|D{$vNKp0KV+u3v&QN3qumsgs5|pYWD0Q5V znL}BELb)4f2#TFfZI8*PQk_P=$!AO*r=Ls(I11T21nj+~PK&!W6`qNKNc-=aOz{&s6TGrU zh58fk|F(>X$fM^fvV_>&D``-Y?o<3o5zU~kZ6Gt)H`5m8&U0xzeie&GAkx9knoDq! zmX4`aB{__)H4=^EE@z%_=YfII1}q5Oo!6RMazp&4l2i9eWj@}$VmTAFjd7uQ7#idFNtYZS<*B*DyzSJV(Zx%;AeVX46~q9Kb^)s?w&AW2jtzE&axlOJc#=gw zOvMO#yD+pATQ65Q$9lLFQtov{IP;MDj4P7+e%R&UV1z5mgdi`|*3N|L*+$fc>C9Ae zi&l!*QO-cOWAwEa#)A(^%Y2tw0E8UlN| zNJS9FDjViox=2P4o@uLufYPDTb)E}hu`w^kI`xW>!hGB2EazkZ)*v=_^TZ6Ti$%2y zN>7@Yb|^E#ZF2=;+?w0g!DJ6MZP?7kJd>v}eBf(0Sf3e5xM@VoaGTZGzPEUK;?AMk zT!CIyZY2k1+F)n8D~!Q|r?V$>M?{2Bq=*O)vxkO|U3E%rigfDFl4vH6b-E2*heZ}` zJW1IjBQkA?PK}Wkiy#EBW!j>7B$+lQPw;4G_(+$g$VyIId-Mc3h47+h2sA(zsHV(v z?w6%nMKMLLTp%$L>L^UJW8jFpG`lm>sSR?64MhpO zwG!brqtpEf)lF4l6+#nvxk<>fGa|!yRCdg5VS|7WDM$F%VM`kdyK-)mjw9q9Uk7_rQab z8v+4~1IMhB7#8lW;9aVQ?}~LIH*0hB|{yS_iY_vV~;MS)jF=f>n|;*djGn z>7v*uMIO({PP7OWQK{H4-Gr)^!BP|sf80Zf{bxfiGGgd2Uf2F_4!OvP^OOgl`Rnn_o^YIcKx!L@rozAH6R4Kw>XoOC?3EYjS;@Ih?rd$LHvj=BBT=vK;MRb+>IV49xyp=F)hFjaa-^Za_X!n|Z< z1-ln%j>2-GcPVG0A}PqdaNRzhqU&QZuzH

8!7%!~QLi+a75l(A%Z4@}9C)lTq_z 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 delta 37783 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 diff --git a/assets/recorder.wit b/assets/recorder.wit index e7c0d02..d23de90 100644 --- a/assets/recorder.wit +++ b/assets/recorder.wit @@ -39,6 +39,9 @@ interface types { // `at` is the step of a host line the guest observes outside its calls. record line { by: party, at: option, event: event } + + // What a line is about: a call, or a stream or future. + variant line-key { task(task-id), handle(label) } } interface %record { @@ -48,11 +51,15 @@ interface %record { } interface replay { - use types.{line}; - // The next line the host performs on its own: in trace order, the first host line that doesn't - // answer a guest call, e.g. an export call, or the host's drop of a guest resource. `none` at - // the end of the trace. Traps if a guest line the guest hasn't done comes before it. + use types.{line, line-key}; + // The next line `start` performs, in trace order: an export call, or the host's drop of a + // resource of the guest. `none` once there are none. next-host-line: func() -> option; + // The next host line about `key`, e.g. the `return` of a STARTED call, which its mock waits + // for. + take-host-line: func(key: line-key) -> option; + // Checks that replay reached every line, once it is done; traps on the first it didn't. + finish: func(); // Checks a guest line against the trace, and returns the host lines that answer it at once, // e.g. the `return` of an import call. Traps on a mismatch, with the expected and the actual // line. diff --git a/components/recorder/src/lib.rs b/components/recorder/src/lib.rs index ccf5db5..28bd52f 100644 --- a/components/recorder/src/lib.rs +++ b/components/recorder/src/lib.rs @@ -13,7 +13,7 @@ mod bindings { } use std::cell::RefCell; -use trace::{Line, Replay, Trace}; +use trace::{Line, LineKey, Replay, Trace}; struct Component; impl bindings::exports::proxy::recorder::record::Guest for Component { @@ -46,6 +46,14 @@ impl bindings::exports::proxy::recorder::replay::Guest for Component { println!("{line}"); Some(line) } + fn take_host_line(key: LineKey) -> Option { + let line = with_replay(|replay| replay.take_host_line(&key))?; + println!("{line}"); + Some(line) + } + fn finish() { + with_replay(|replay| replay.finish()); + } fn check(line: Line) -> Vec { println!("{line}"); // A replayed `wasi:cli/exit` ends the replay with the guest's status. diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 4b50b17..eee0d1b 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -58,6 +58,30 @@ impl Line { ) && self.event.task().is_some_and(|id| id.table == Table::Imports) } + /// Whether the line is one the host performs from the exports component, which replay's + /// `start` performs: an export call, or the host's drop of a resource of the guest. The + /// host's drops of its own resources, in the imports table, the guest doesn't see; replay has + /// nothing to do for them. + pub fn is_start_line(&self) -> bool { + self.by == Party::Host + && match &self.event { + Event::Call(_) => true, + Event::Drop(label) => label.table == Table::Exports, + _ => false, + } + } + /// Whether the line is about `key`: a line of the task, or of the stream or future. + pub fn is_about(&self, key: &LineKey) -> bool { + match (key, &self.event) { + (LineKey::Task(task), event) => event.task() == Some(*task), + (LineKey::Handle(label), Event::Write(chunk) | Event::Read(chunk)) => { + &chunk.stream == label + } + (LineKey::Handle(label), Event::Value(value)) => &value.future == label, + (LineKey::Handle(label), Event::Drop(handle)) => handle == label, + _ => false, + } + } /// The exit status of a call to `wasi:cli/exit`, which ends the run. pub fn exit_status(&self) -> Option { match &self.event { @@ -173,22 +197,36 @@ impl Replay { .enumerate() .filter(|(idx, _)| !self.done[*idx]) } - /// The next line the host performs on its own: in trace order, the first host line that - /// doesn't answer a guest call, e.g. an export call, or the host's drop of a guest resource. - /// `None` at the end of the trace. - /// - /// Panics if a guest line comes before it, which the guest hasn't done. + /// The next line replay's `start` performs, in trace order: an export call, or the host's + /// drop of a resource of the guest (see `Line::is_start_line`). `None` once there are none. + /// The lines before it that are still pending may happen while `start` waits for its step; + /// `finish` reports those that never do. pub fn next_host_line(&mut self) -> Option { - let (idx, line) = self - .pending() - .find(|(_, line)| line.by == Party::Guest || !line.answers_guest())?; - if line.by == Party::Guest { - panic!("the trace has `{line}`, which the guest didn't do"); - } + self.take(|line| line.is_start_line()) + } + /// The next host line about `key`, e.g. the `Return` of a STARTED call, which the import's + /// mock waits for, or a copy of a stream, which a job performs. + pub fn take_host_line(&mut self, key: &LineKey) -> Option { + self.take(|line| line.by == Party::Host && line.is_about(key)) + } + fn take(&mut self, matches: impl Fn(&Line) -> bool) -> Option { + let (idx, line) = self.pending().find(|(_, line)| matches(line))?; let line = line.clone(); self.done[idx] = true; Some(line) } + /// Checks that replay reached every line, once it is done: all but the host's drops of its + /// own resources, which replay has nothing to do for. + /// + /// Panics on the first line it didn't reach. + pub fn finish(&self) { + let missed = self.pending().find(|(_, line)| { + line.by == Party::Guest || line.is_start_line() || !matches!(line.event, Event::Drop(_)) + }); + if let Some((_, line)) = missed { + panic!("the trace has `{line}`, which replay didn't reach"); + } + } /// Checks a guest line against the trace, and returns the host lines that answer it at once, /// e.g. the `Return` of an import call. A guest line is matched by its task, for the lines of /// a call, and otherwise as the trace's next guest line. @@ -338,11 +376,27 @@ mod tests { } #[test] - #[should_panic(expected = "the trace has `guest: call c1 get(1)`, which the guest didn't do")] + #[should_panic(expected = "the trace has `guest: call c1 get(1)`, which replay didn't reach")] fn replay_missing_guest_line() { let mut replay = Replay::new(p2_trace()); - replay.next_host_line(); - replay.next_host_line(); + // The guest's lines may still come while `start` waits for the next line's step. + assert_eq!(replay.next_host_line(), Some(p2_trace().0[0].clone())); + assert_eq!(replay.next_host_line(), Some(p2_trace().0[4].clone())); + assert_eq!(replay.next_host_line(), None); + replay.finish(); + } + + #[test] + fn host_drops_of_mocks() { + let mock = Label { + table: Table::Imports, + name: "#counter-1".to_string(), + }; + let trace = Trace(vec![line(Party::Host, Event::Drop(mock))]); + let mut replay = Replay::new(trace); + // Neither `start`'s, nor needed. + assert_eq!(replay.next_host_line(), None); + replay.finish(); } #[test] @@ -364,8 +418,10 @@ mod tests { late.clone(), ]); let mut replay = Replay::new(trace.clone()); - // The `Return` comes at its step, not in answer to the call. + // The `Return` comes at its step, not in answer to the call, and the mock takes it. assert_eq!(replay.check(&trace.0[0]), vec![trace.0[1].clone()]); - assert_eq!(replay.next_host_line(), Some(late)); + assert_eq!(replay.next_host_line(), None); + assert_eq!(replay.take_host_line(&LineKey::Task(c1)), Some(late)); + replay.finish(); } } diff --git a/src/ast.rs b/src/ast.rs index 3c7afdc..0f854f6 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -306,13 +306,11 @@ impl<'a> Opt<'a> { out.push_str(&format!( "get-mock-{func_name}: func(handle: u32) -> {func_name};\n" )); - // The handle of a mock in the component that made it, which labels it in - // both generated components. - if matches!(self.mode, Mode::Fuzz | Mode::Dialog) { - out.push_str(&format!( - "get-handle-mock-{func_name}: func(x: borrow<{func_name}>) -> u32;\n" - )); - } + // The label of a mock, which the imports component, which made it, keeps, + // so that both generated components label it alike. + out.push_str(&format!( + "get-handle-mock-{func_name}: func(x: borrow<{func_name}>) -> u32;\n" + )); } } } diff --git a/src/codegen/fuzz.rs b/src/codegen/fuzz.rs index 4e2617c..125c031 100644 --- a/src/codegen/fuzz.rs +++ b/src/codegen/fuzz.rs @@ -129,26 +129,30 @@ impl State { } else { quote! { None } }; + // The arguments the call borrowed are dropped at the end of the inner block, which + // are the host's drops, not the guest's. quote! { { - let mut __params: Vec = Vec::new(); - #( - let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); - )* - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record(&Line { - at: Some(__at), - ..trace_call(Party::Host, __id, #display_name, #is_async, __params) - }); - let res = #call; - proxy::recorder::record::record(&trace_return(Party::Guest, __id, #value)); + let res = { + let mut __params: Vec = Vec::new(); + #( + let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); + )* + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record(&Line { + at: Some(__at), + ..trace_call(Party::Host, __id, #display_name, #is_async, __params) + }); + let res = #call; + proxy::recorder::record::record(&trace_return(Party::Guest, __id, #value)); + proxy::conversion::executor::host_drops(true); + res + }; // The streams and futures the guest returns, which the host reads or // drops, and its resources, which the host drops. The host's own - // resources, and the mocks of the borrowed arguments, are the host's - // drops. - proxy::conversion::executor::host_drops(true); + // resources are the host's drops too. Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); diff --git a/src/codegen/mock.rs b/src/codegen/mock.rs index bd77a77..81f0dd8 100644 --- a/src/codegen/mock.rs +++ b/src/codegen/mock.rs @@ -24,11 +24,9 @@ impl State { }, _ => quote! { let name = wasm_wave::to_string(&res.to_value()).unwrap(); - let line = if MOVING.with(|moving| moving.get()) { - Line { at: HOST_DROP_AT.get(), ..trace_drop(Party::Host, Table::Imports, name) } - } else { - trace_drop(Party::Guest, Table::Imports, name) - }; + // The host's take no step, as the guest doesn't see them. + let by = if MOVING.with(|moving| moving.get()) { Party::Host } else { Party::Guest }; + let line = trace_drop(by, Table::Imports, name); proxy::recorder::record::record(&line); }, }; @@ -44,19 +42,10 @@ impl State { } /// Items the mocks use. pub fn mock_defs(&self) -> Vec { - // The step of the host's drops, when the exports component makes them, rather than a - // mock in answer to a call (see `host-drops`). - let host_drop_at = match self.mode { - Mode::Fuzz => quote! { - static HOST_DROP_AT: std::cell::Cell> = const { std::cell::Cell::new(None) }; - }, - _ => quote! {}, - }; let file: syn::File = parse_quote! { thread_local! { // The label of the last mock. static MOCK_LABEL: std::cell::Cell = const { std::cell::Cell::new(0) }; - #host_drop_at } /// The label of a new mock. #[allow(dead_code)] diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index a159e2a..bf16635 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -376,6 +376,11 @@ impl State { Mode::Dialog => quote! { mut __executor }, _ => quote! { __executor }, }; + // Replay checks that it reached every line, once everything is done. + let finish = match self.mode { + Mode::Replay => quote! { proxy::recorder::replay::finish(); }, + _ => quote! {}, + }; parse_quote! { #sig { let #executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); @@ -383,6 +388,7 @@ impl State { join_spawned().await; proxy::conversion::executor::stop(); __executor.await; + #finish } } } @@ -398,11 +404,8 @@ impl State { ("run", _) => quote! { run_executor().await }, ("stop", _) => quote! { stop_executor() }, ("step", _) => quote! { next_step() }, - ("host_drops", Mode::Fuzz) => quote! { - MOVING.with(|moving| moving.set(dropping)); - HOST_DROP_AT.set(dropping.then(next_step)); - }, - ("host_drops", Mode::Replay) => { + ("wait_until_step", _) => quote! { wait_until_step(step).await }, + ("host_drops", Mode::Replay | Mode::Fuzz) => { quote! { MOVING.with(|moving| moving.set(dropping)) } } ("host_drops", _) => quote! { let _ = dropping; }, @@ -438,7 +441,7 @@ enum HasExecutor { /// executor's run in the imports component, and a step of its own, as an export call's arrival, /// in the exports component; and, in fuzz mode, `host_dropping`, which runs `f`, dropping mocks /// for the host, e.g. those in a value a job read from the guest, so that the trace has the -/// host's drops, in the step they happen in. +/// host's drops. Those take no step: the guest doesn't see them. fn job_defs(mode: &Mode, has_executor: Option) -> Vec { let is_fuzz = matches!(mode, Mode::Fuzz); let file: File = match has_executor { @@ -448,10 +451,8 @@ fn job_defs(mode: &Mode, has_executor: Option) -> Vec { #[allow(dead_code)] fn host_dropping(f: impl FnOnce() -> R) -> R { MOVING.with(|moving| moving.set(true)); - HOST_DROP_AT.set(Some(current_step())); let res = f(); MOVING.with(|moving| moving.set(false)); - HOST_DROP_AT.set(None); res } } @@ -490,7 +491,28 @@ fn job_defs(mode: &Mode, has_executor: Option) -> Vec { } None => return Vec::new(), }; - file.items + let mut items = file.items; + if matches!(mode, Mode::Replay) && matches!(has_executor, Some(HasExecutor::Exports)) { + let file: File = parse_quote! { + /// The value of the `return` that answers an async call: at once, if the trace has + /// the call RETURNED, or, if STARTED, in the step of its `return`, which the mock + /// waits for. + async fn trace_answer(id: TaskId, answers: Vec) -> Option { + if !answers.iter().any(|line| matches!(line.event, Event::Started(_))) { + return trace_return_value(answers); + } + let line = proxy::recorder::replay::take_host_line(&LineKey::Task(id)) + .expect("the trace doesn't return the call"); + let Event::Return(ret) = line.event else { + panic!("the trace answers the call with {:?}", line.event); + }; + wait_until_step(line.at.expect("a STARTED call returns in a step")).await; + ret.value + } + }; + items.extend(file.items); + } + items } /// Helpers of record, replay and fuzz modes, which make the lines of `proxy:recorder/types`. Each diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index fe7f48b..0ace9b1 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -17,11 +17,17 @@ impl State { let is_async = wit_func.is_async; let args = extract_arg_info(sig); let ret_ty = get_return_type(&sig.output); - // Checks the call, and takes the value of the `return` that answers it. + // Checks the call, and takes the value of the `return` that answers it: for an async + // call, at once or in its step, as the trace has it RETURNED or STARTED. + let answer = if is_async { + quote! { trace_answer(__id, proxy::recorder::replay::check(&__call)).await } + } else { + quote! { trace_return_value(proxy::recorder::replay::check(&__call)) } + }; let check_call = quote! { let __id = trace_task_id(Table::Imports); let __call = trace_call(Party::Guest, __id, #display_name, #is_async, __params); - let wave = trace_return_value(proxy::recorder::replay::check(&__call)); + let wave = #answer; }; let replay_import = if let Some(ret_ty) = ret_ty { let to_rust = match wit_func.constructor_resource() { @@ -129,15 +135,23 @@ impl State { } pub fn generate_replay_start_func(&self, sig: &Signature) -> syn::ImplItemFn { let arms = self.export_funcs().into_iter().map(|func| { - let call = func.call(); let ExportFunc { display_name, + func, arg_names, arg_tys, + call_params, has_ret, ret_owns, + is_async, .. } = func; + // An async call that blocks gives the host a subtask, which the guest started. + let call = if is_async { + quote! { call_export(__id, #func(#(#call_params),*)).await } + } else { + quote! { #func(#(#call_params),*) } + }; let arg_idx = (0..arg_names.len()).map(|idx| quote! { args[#idx] }); // The export call's id is the trace's, as the trace makes the call. let assert_ret = if has_ret { @@ -157,13 +171,20 @@ impl State { } else { quote! {} }; + // The arguments the call borrowed are dropped at the end of the block, which are + // the host's drops, as in fuzz mode, not the guest's. quote! { #display_name => { - #( - let arg_value: Value = wasm_wave::from_str(&<#arg_tys as ValueTyped>::value_type(), &#arg_idx).unwrap(); - let #arg_names: #arg_tys = arg_value.to_rust(); - )* - let res = #call; + let res = { + #( + let arg_value: Value = wasm_wave::from_str(&<#arg_tys as ValueTyped>::value_type(), &#arg_idx).unwrap(); + let #arg_names: #arg_tys = arg_value.to_rust(); + )* + let res = #call; + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); #assert_ret #keep_ret } @@ -173,7 +194,29 @@ impl State { self.generate_start( sig, quote! { + /// Calls an async export, and checks that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + proxy::recorder::replay::check(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); + } + poll + }) + .await + } while let Some(line) = proxy::recorder::replay::next_host_line() { + // A line in step `at` follows the step before it, and takes its own step, as + // an export call's arrival does. + if let Some(at) = line.at { + proxy::conversion::executor::wait_until_step(at - 1).await; + let step = proxy::conversion::executor::step(); + assert_eq!(step, at, "replay came to {:?} in step {step}", line.event); + } match line.event { Event::Call(Call { id: __id, func, args, .. }) => { match func.as_str() { @@ -182,12 +225,14 @@ impl State { } } #drop_arm - event => unreachable!("the host can't do {event:?} in a p2 trace"), + event => unreachable!("`start` doesn't perform {event:?}"), } // clean up borrowed resources from input args + proxy::conversion::executor::host_drops(true); SCOPED_ALLOC.with(|alloc| { alloc.borrow_mut().clear(); }); + proxy::conversion::executor::host_drops(false); } }, ) diff --git a/src/run.rs b/src/run.rs index 3aa0f0f..d6f66a1 100644 --- a/src/run.rs +++ b/src/run.rs @@ -54,6 +54,15 @@ impl bindings::proxy::recorder::replay::Host for State { println!("{line}"); Some(trace::convert(line)) } + fn take_host_line(&mut self, key: bindings::proxy::recorder::types::LineKey) -> Option { + let key: trace::LineKey = trace::convert(key); + let line = self.replay.as_mut().unwrap().take_host_line(&key)?; + println!("{line}"); + Some(trace::convert(line)) + } + fn finish(&mut self) { + self.replay.as_ref().unwrap().finish(); + } fn check(&mut self, line: Line) -> Vec { let line: trace::Line = trace::convert(line); println!("{line}"); diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 0b2c744..3d907c0 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -135,12 +135,13 @@ impl<'a> TraitGenerator<'a> { traits.push(Box::new(proxy::ProxyTrait::new(state))); } Mode::Replay => { + // A mock carries the trace's label, which the exports component prints it by too. traits.push(Box::new(wave::WaveTrait { to_value: true, to_rust: true, has_replay_table: true, wrapped_labels: false, - mock_labels: false, + mock_labels: true, })); } Mode::Fuzz => { diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index 8d2eb20..cf8b809 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -29,6 +29,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + async fn wait_until_step(step: u32) -> () { + wait_until_step(step).await + } fn host_drops(dropping: bool) -> () { let _ = dropping; } @@ -706,38 +709,67 @@ fn current_step() -> u32 { STEP.get() } /// Waits, from any task, until the executor has run `runs` more times, and returns the step of -/// the last run. A job counts them, waking the executor itself in between. +/// the last run. #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is +/// already there, e.g. replay's for a host line recorded in that step. +/// +/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() == step { + return; + } + wait_for_run(move || { + let now = current_step(); + assert!( + now <= step, "replay reached step {now} before the run in step {step}" + ); + now == step + }) + .await; +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, } - let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_done = done.clone(); - let mut left = runs; + let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_shared = shared.clone(); spawn( std::future::poll_fn(move |_| { - if left == 0 { - let mut done = job_done.borrow_mut(); - done.step = Some(current_step()); - if let Some(waker) = done.waker.take() { + if done() { + let mut shared = job_shared.borrow_mut(); + shared.step = Some(current_step()); + if let Some(waker) = shared.waker.take() { waker.wake(); } return std::task::Poll::Ready(()); } - left -= 1; WAKE_AGAIN.set(true); std::task::Poll::Pending }), ); std::future::poll_fn(move |cx| { - let mut done = done.borrow_mut(); - match done.step { + let mut shared = shared.borrow_mut(); + match shared.step { Some(step) => std::task::Poll::Ready(step), None => { - done.waker = Some(cx.waker().clone()); + shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending } } diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index edaec4e..94e30ce 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -35,34 +35,39 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let idx = u.int_in_range(1..=9usize).unwrap(); match idx { 1usize => { - let mut __params: Vec = Vec::new(); - let p: test::guest::api::Point = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); - let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let p: test::guest::api::Point = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); + let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.run", + false, + __params, + ) + }, + ); + let res = test::guest::api::run(&p, &c); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:guest/api@0.1.0.run", - false, - __params, - ) - }, - ); - let res = test::guest::api::run(&p, &c); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -71,32 +76,36 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 2usize => { - let mut __params: Vec = Vec::new(); - let s: test::guest::api::Shape = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let s: test::guest::api::Shape = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.classify", + false, + __params, + ) + }, + ); + let res = test::guest::api::classify(&s); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:guest/api@0.1.0.classify", - false, - __params, - ) - }, - ); - let res = test::guest::api::classify(&s); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -105,32 +114,36 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 3usize => { - let mut __params: Vec = Vec::new(); - let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let c: test::guest::api::Counter = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.begin", + false, + __params, + ) + }, + ); + let res = test::guest::api::begin(c); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:guest/api@0.1.0.begin", - false, - __params, - ) - }, - ); - let res = test::guest::api::begin(c); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -139,33 +152,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 4usize => { - let mut __params: Vec = Vec::new(); - let name: String = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let name: String = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&name)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "new test:guest/api/session@0.1.0", + false, + __params, + ) + }, + ); + let res = test::guest::api::Session::new(&name); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "new test:guest/api/session@0.1.0", - false, - __params, - ) - }, - ); - let res = test::guest::api::Session::new(&name); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -174,34 +192,39 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 5usize => { - let mut __params: Vec = Vec::new(); - let x: i32 = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); - let y: i32 = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let x: i32 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); + let y: i32 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types@0.1.0.make-point", + false, + __params, + ) + }, + ); + let res = test::host::types::make_point(x, y); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:host/types@0.1.0.make-point", - false, - __params, - ) - }, - ); - let res = test::host::types::make_point(x, y); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -210,34 +233,39 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 6usize => { - let mut __params: Vec = Vec::new(); - let p: test::host::types::Perms = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); - let c: test::host::types::Color = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let p: test::host::types::Perms = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); + let c: test::host::types::Color = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types@0.1.0.check", + false, + __params, + ) + }, + ); + let res = test::host::types::check(p, c); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:host/types@0.1.0.check", - false, - __params, - ) - }, - ); - let res = test::host::types::check(p, c); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -246,33 +274,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 7usize => { - let mut __params: Vec = Vec::new(); - let name: String = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let name: String = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&name)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types@0.1.0.open", + false, + __params, + ) + }, + ); + let res = test::host::types::open(&name); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:host/types@0.1.0.open", - false, - __params, - ) - }, - ); - let res = test::host::types::open(&name); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -281,33 +314,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 8usize => { - let mut __params: Vec = Vec::new(); - let start: u32 = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let start: u32 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&start)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "new test:host/types/counter@0.1.0", + false, + __params, + ) + }, + ); + let res = test::host::types::Counter::new(start); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "new test:host/types/counter@0.1.0", - false, - __params, - ) - }, - ); - let res = test::host::types::Counter::new(start); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -316,34 +354,39 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 9usize => { - let mut __params: Vec = Vec::new(); - let a: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); - let b: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let a: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); + let b: test::host::types::Counter = Fuzz::fuzz(&mut u).unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:host/types/counter@0.1.0.merge", + false, + __params, + ) + }, + ); + let res = test::host::types::Counter::merge(&a, b); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:host/types/counter@0.1.0.merge", - false, - __params, - ) - }, - ); - let res = test::host::types::Counter::merge(&a, b); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -865,8 +908,6 @@ thread_local! { } thread_local! { static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; - static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { - std::cell::Cell::new(None) }; } /// The label of a new mock. #[allow(dead_code)] diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index d213380..5b31e40 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -28,9 +28,11 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + async fn wait_until_step(step: u32) -> () { + wait_until_step(step).await + } fn host_drops(dropping: bool) -> () { - MOVING.with(|moving| moving.set(dropping)); - HOST_DROP_AT.set(dropping.then(next_step)); + MOVING.with(|moving| moving.set(dropping)) } } impl exports::test::host::types::Guest for Stub { @@ -270,14 +272,12 @@ impl exports::test::host::types::GuestCounter for MockedResource { unsafe fn resource_from_raw_(handle: *mut Option) -> Option { if let Some(res) = *unsafe { Box::from_raw(handle) } { let name = wasm_wave::to_string(&res.to_value()).unwrap(); - let line = if MOVING.with(|moving| moving.get()) { - Line { - at: HOST_DROP_AT.get(), - ..trace_drop(Party::Host, Table::Imports, name) - } + let by = if MOVING.with(|moving| moving.get()) { + Party::Host } else { - trace_drop(Party::Guest, Table::Imports, name) + Party::Guest }; + let line = trace_drop(by, Table::Imports, name); proxy::recorder::record::record(&line); } None @@ -768,10 +768,8 @@ fn trace_at() -> u32 { #[allow(dead_code)] fn host_dropping(f: impl FnOnce() -> R) -> R { MOVING.with(|moving| moving.set(true)); - HOST_DROP_AT.set(Some(current_step())); let res = f(); MOVING.with(|moving| moving.set(false)); - HOST_DROP_AT.set(None); res } thread_local! { @@ -780,8 +778,6 @@ thread_local! { } thread_local! { static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; - static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { - std::cell::Cell::new(None) }; } /// The label of a new mock. #[allow(dead_code)] @@ -814,38 +810,67 @@ fn current_step() -> u32 { STEP.get() } /// Waits, from any task, until the executor has run `runs` more times, and returns the step of -/// the last run. A job counts them, waking the executor itself in between. +/// the last run. #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is +/// already there, e.g. replay's for a host line recorded in that step. +/// +/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() == step { + return; + } + wait_for_run(move || { + let now = current_step(); + assert!( + now <= step, "replay reached step {now} before the run in step {step}" + ); + now == step + }) + .await; +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, } - let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_done = done.clone(); - let mut left = runs; + let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_shared = shared.clone(); spawn( std::future::poll_fn(move |_| { - if left == 0 { - let mut done = job_done.borrow_mut(); - done.step = Some(current_step()); - if let Some(waker) = done.waker.take() { + if done() { + let mut shared = job_shared.borrow_mut(); + shared.step = Some(current_step()); + if let Some(waker) = shared.waker.take() { waker.wake(); } return std::task::Poll::Ready(()); } - left -= 1; WAKE_AGAIN.set(true); std::task::Poll::Pending }), ); std::future::poll_fn(move |cx| { - let mut done = done.borrow_mut(); - match done.step { + let mut shared = shared.borrow_mut(); + match shared.step { Some(step) => std::task::Poll::Ready(step), None => { - done.waker = Some(cx.waker().clone()); + shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending } } diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index 4338977..dfb7cda 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -29,6 +29,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + async fn wait_until_step(step: u32) -> () { + wait_until_step(step).await + } fn host_drops(dropping: bool) -> () { let _ = dropping; } @@ -623,38 +626,67 @@ fn current_step() -> u32 { STEP.get() } /// Waits, from any task, until the executor has run `runs` more times, and returns the step of -/// the last run. A job counts them, waking the executor itself in between. +/// the last run. #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is +/// already there, e.g. replay's for a host line recorded in that step. +/// +/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() == step { + return; + } + wait_for_run(move || { + let now = current_step(); + assert!( + now <= step, "replay reached step {now} before the run in step {step}" + ); + now == step + }) + .await; +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, } - let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_done = done.clone(); - let mut left = runs; + let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_shared = shared.clone(); spawn( std::future::poll_fn(move |_| { - if left == 0 { - let mut done = job_done.borrow_mut(); - done.step = Some(current_step()); - if let Some(waker) = done.waker.take() { + if done() { + let mut shared = job_shared.borrow_mut(); + shared.step = Some(current_step()); + if let Some(waker) = shared.waker.take() { waker.wake(); } return std::task::Poll::Ready(()); } - left -= 1; WAKE_AGAIN.set(true); std::task::Poll::Pending }), ); std::future::poll_fn(move |cx| { - let mut done = done.borrow_mut(); - match done.step { + let mut shared = shared.borrow_mut(); + match shared.step { Some(step) => std::task::Poll::Ready(step), None => { - done.waker = Some(cx.waker().clone()); + shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending } } diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index cc2f3f4..4d59ac4 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -35,33 +35,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let idx = u.int_in_range(1..=9usize).unwrap(); match idx { 1usize => { - let mut __params: Vec = Vec::new(); - let url: String = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let url: String = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&url)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/api@0.1.0.handle", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::api::handle(url)).await; + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:p3/api@0.1.0.handle", - true, - __params, - ) - }, - ); - let res = call_export(__id, test::p3::api::handle(url)).await; - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -70,33 +75,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 2usize => { - let mut __params: Vec = Vec::new(); - let port: u16 = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&port)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let port: u16 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&port)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/api@0.1.0.serve", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::api::serve(port)).await; + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:p3/api@0.1.0.serve", - true, - __params, - ) - }, - ); - let res = call_export(__id, test::p3::api::serve(port)).await; - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -105,35 +115,39 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 3usize => { - let mut __params: Vec = Vec::new(); - let s: wit_bindgen::rt::async_support::StreamReader = Fuzz::fuzz( - &mut u, - ) - .unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, - __id, - "test:p3/api@0.1.0.echo", - false, - __params, + let res = { + let mut __params: Vec = Vec::new(); + let s: wit_bindgen::rt::async_support::StreamReader = Fuzz::fuzz( + &mut u, ) - }, - ); - let res = test::p3::api::echo(s); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + .unwrap(); + __params + .push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/api@0.1.0.echo", + false, + __params, + ) + }, + ); + let res = test::p3::api::echo(s); + proxy::recorder::record::record( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -142,33 +156,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 4usize => { - let mut __params: Vec = Vec::new(); - let url: String = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&url)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let url: String = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&url)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.fetch", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::host::fetch(url)).await; + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:p3/host@0.1.0.fetch", - true, - __params, - ) - }, - ); - let res = call_export(__id, test::p3::host::fetch(url)).await; - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -177,29 +196,34 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 5usize => { - let mut __params: Vec = Vec::new(); - let ms: u64 = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, - __id, - "test:p3/host@0.1.0.sleep", - true, - __params, - ) - }, - ); - let res = call_export(__id, test::p3::host::sleep(ms)).await; - proxy::recorder::record::record( - &trace_return(Party::Guest, __id, None), - ); - proxy::conversion::executor::host_drops(true); + let res = { + let mut __params: Vec = Vec::new(); + let ms: u64 = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.sleep", + true, + __params, + ) + }, + ); + let res = call_export(__id, test::p3::host::sleep(ms)).await; + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, None), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -208,32 +232,37 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 6usize => { - let mut __params: Vec = Vec::new(); - let err: wit_bindgen::rt::async_support::ErrorContext = Fuzz::fuzz( - &mut u, - ) - .unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&err)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, - __id, - "test:p3/host@0.1.0.report", - false, - __params, + let res = { + let mut __params: Vec = Vec::new(); + let err: wit_bindgen::rt::async_support::ErrorContext = Fuzz::fuzz( + &mut u, ) - }, - ); - let res = test::p3::host::report(err); - proxy::recorder::record::record( - &trace_return(Party::Guest, __id, None), - ); - proxy::conversion::executor::host_drops(true); + .unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&err)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.report", + false, + __params, + ) + }, + ); + let res = test::p3::host::report(err); + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, None), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -242,31 +271,36 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 7usize => { - let mut __params: Vec = Vec::new(); - let done: wit_bindgen::rt::async_support::FutureReader< - ::core::result::Result<(), test::p3::host::ErrorCode>, - > = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&done)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, - __id, - "test:p3/host@0.1.0.finish", - false, - __params, - ) - }, - ); - let res = test::p3::host::finish(done); - proxy::recorder::record::record( - &trace_return(Party::Guest, __id, None), - ); - proxy::conversion::executor::host_drops(true); + let res = { + let mut __params: Vec = Vec::new(); + let done: wit_bindgen::rt::async_support::FutureReader< + ::core::result::Result<(), test::p3::host::ErrorCode>, + > = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&done)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host@0.1.0.finish", + false, + __params, + ) + }, + ); + let res = test::p3::host::finish(done); + proxy::recorder::record::record( + &trace_return(Party::Guest, __id, None), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -275,37 +309,40 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 8usize => { - let mut __params: Vec = Vec::new(); - let contents: ::core::option::Option< - wit_bindgen::rt::async_support::StreamReader, - > = Fuzz::fuzz(&mut u).unwrap(); - __params - .push( - wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap(), + let res = { + let mut __params: Vec = Vec::new(); + let contents: ::core::option::Option< + wit_bindgen::rt::async_support::StreamReader, + > = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "new test:p3/host/body@0.1.0", + false, + __params, + ) + }, ); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = test::p3::host::Body::new(contents); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "new test:p3/host/body@0.1.0", - false, - __params, - ) - }, - ); - let res = test::p3::host::Body::new(contents); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -314,33 +351,38 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } 9usize => { - let mut __params: Vec = Vec::new(); - let this: test::p3::host::Body = Fuzz::fuzz(&mut u).unwrap(); - __params - .push(wasm_wave::to_string(&ToValue::to_value(&this)).unwrap()); - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record( - &Line { - at: Some(__at), - ..trace_call( - Party::Host, + let res = { + let mut __params: Vec = Vec::new(); + let this: test::p3::host::Body = Fuzz::fuzz(&mut u).unwrap(); + __params + .push( + wasm_wave::to_string(&ToValue::to_value(&this)).unwrap(), + ); + let __id = trace_task_id(Table::Exports); + let __at = proxy::conversion::executor::step(); + proxy::recorder::record::record( + &Line { + at: Some(__at), + ..trace_call( + Party::Host, + __id, + "test:p3/host/body@0.1.0.consume", + false, + __params, + ) + }, + ); + let res = test::p3::host::Body::consume(this); + proxy::recorder::record::record( + &trace_return( + Party::Guest, __id, - "test:p3/host/body@0.1.0.consume", - false, - __params, - ) - }, - ); - let res = test::p3::host::Body::consume(this); - proxy::recorder::record::record( - &trace_return( - Party::Guest, - __id, - Some(wasm_wave::to_string(&res.to_value()).unwrap()), - ), - ); - proxy::conversion::executor::host_drops(true); + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); + proxy::conversion::executor::host_drops(true); + res + }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); @@ -866,8 +908,6 @@ thread_local! { } thread_local! { static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; - static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { - std::cell::Cell::new(None) }; } /// The label of a new mock. #[allow(dead_code)] diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 269dd38..9e93eae 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -28,9 +28,11 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + async fn wait_until_step(step: u32) -> () { + wait_until_step(step).await + } fn host_drops(dropping: bool) -> () { - MOVING.with(|moving| moving.set(dropping)); - HOST_DROP_AT.set(dropping.then(next_step)); + MOVING.with(|moving| moving.set(dropping)) } } impl exports::test::p3::host::Guest for Stub { @@ -308,14 +310,12 @@ impl exports::test::p3::host::GuestBody for MockedResource { unsafe fn resource_from_raw_(handle: *mut Option) -> Option { if let Some(res) = *unsafe { Box::from_raw(handle) } { let name = wasm_wave::to_string(&res.to_value()).unwrap(); - let line = if MOVING.with(|moving| moving.get()) { - Line { - at: HOST_DROP_AT.get(), - ..trace_drop(Party::Host, Table::Imports, name) - } + let by = if MOVING.with(|moving| moving.get()) { + Party::Host } else { - trace_drop(Party::Guest, Table::Imports, name) + Party::Guest }; + let line = trace_drop(by, Table::Imports, name); proxy::recorder::record::record(&line); } None @@ -813,10 +813,8 @@ fn trace_at() -> u32 { #[allow(dead_code)] fn host_dropping(f: impl FnOnce() -> R) -> R { MOVING.with(|moving| moving.set(true)); - HOST_DROP_AT.set(Some(current_step())); let res = f(); MOVING.with(|moving| moving.set(false)); - HOST_DROP_AT.set(None); res } thread_local! { @@ -825,8 +823,6 @@ thread_local! { } thread_local! { static MOCK_LABEL : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; - static HOST_DROP_AT : std::cell::Cell < Option < u32 >> = const { - std::cell::Cell::new(None) }; } /// The label of a new mock. #[allow(dead_code)] @@ -859,38 +855,67 @@ fn current_step() -> u32 { STEP.get() } /// Waits, from any task, until the executor has run `runs` more times, and returns the step of -/// the last run. A job counts them, waking the executor itself in between. +/// the last run. #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is +/// already there, e.g. replay's for a host line recorded in that step. +/// +/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() == step { + return; + } + wait_for_run(move || { + let now = current_step(); + assert!( + now <= step, "replay reached step {now} before the run in step {step}" + ); + now == step + }) + .await; +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, } - let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_done = done.clone(); - let mut left = runs; + let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_shared = shared.clone(); spawn( std::future::poll_fn(move |_| { - if left == 0 { - let mut done = job_done.borrow_mut(); - done.step = Some(current_step()); - if let Some(waker) = done.waker.take() { + if done() { + let mut shared = job_shared.borrow_mut(); + shared.step = Some(current_step()); + if let Some(waker) = shared.waker.take() { waker.wake(); } return std::task::Poll::Ready(()); } - left -= 1; WAKE_AGAIN.set(true); std::task::Poll::Pending }), ); std::future::poll_fn(move |cx| { - let mut done = done.borrow_mut(); - match done.step { + let mut shared = shared.borrow_mut(); + match shared.step { Some(step) => std::task::Poll::Ready(step), None => { - done.waker = Some(cx.waker().clone()); + shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending } } diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index a9fd952..a4883b0 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -5,24 +5,58 @@ bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); + /// Calls an async export, and checks that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export( + id: TaskId, + call: impl std::future::Future, + ) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + proxy::recorder::replay::check( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await + } while let Some(line) = proxy::recorder::replay::next_host_line() { + if let Some(at) = line.at { + proxy::conversion::executor::wait_until_step(at - 1).await; + let step = proxy::conversion::executor::step(); + assert_eq!(step, at, "replay came to {:?} in step {step}", line.event); + } match line.event { Event::Call(Call { id: __id, func, args, .. }) => { match func.as_str() { "test:guest/api@0.1.0.run" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let p: test::guest::api::Point = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let c: test::guest::api::Counter = arg_value.to_rust(); - let res = test::guest::api::run(&p, &c); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let p: test::guest::api::Point = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let c: test::guest::api::Counter = arg_value.to_rust(); + let res = test::guest::api::run(&p, &c); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -30,13 +64,18 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ); } "test:guest/api@0.1.0.classify" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let s: test::guest::api::Shape = arg_value.to_rust(); - let res = test::guest::api::classify(&s); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let s: test::guest::api::Shape = arg_value.to_rust(); + let res = test::guest::api::classify(&s); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -44,13 +83,18 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ); } "test:guest/api@0.1.0.begin" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let c: test::guest::api::Counter = arg_value.to_rust(); - let res = test::guest::api::begin(c); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let c: test::guest::api::Counter = arg_value.to_rust(); + let res = test::guest::api::begin(c); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -59,13 +103,18 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { std::mem::forget(res); } "new test:guest/api/session@0.1.0" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let name: String = arg_value.to_rust(); - let res = test::guest::api::Session::new(&name); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let name: String = arg_value.to_rust(); + let res = test::guest::api::Session::new(&name); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -74,19 +123,24 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { std::mem::forget(res); } "test:host/types@0.1.0.make-point" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let x: i32 = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let y: i32 = arg_value.to_rust(); - let res = test::host::types::make_point(x, y); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let x: i32 = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let y: i32 = arg_value.to_rust(); + let res = test::host::types::make_point(x, y); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -94,19 +148,24 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ); } "test:host/types@0.1.0.check" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let p: test::host::types::Perms = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let c: test::host::types::Color = arg_value.to_rust(); - let res = test::host::types::check(p, c); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let p: test::host::types::Perms = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let c: test::host::types::Color = arg_value.to_rust(); + let res = test::host::types::check(p, c); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -114,13 +173,18 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { ); } "test:host/types@0.1.0.open" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let name: String = arg_value.to_rust(); - let res = test::host::types::open(&name); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let name: String = arg_value.to_rust(); + let res = test::host::types::open(&name); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -129,13 +193,18 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { std::mem::forget(res); } "new test:host/types/counter@0.1.0" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let start: u32 = arg_value.to_rust(); - let res = test::host::types::Counter::new(start); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let start: u32 = arg_value.to_rust(); + let res = test::host::types::Counter::new(start); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -144,19 +213,24 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { std::mem::forget(res); } "test:host/types/counter@0.1.0.merge" => { - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[0usize], - ) - .unwrap(); - let a: test::host::types::Counter = arg_value.to_rust(); - let arg_value: Value = wasm_wave::from_str( - &::value_type(), - &args[1usize], - ) - .unwrap(); - let b: test::host::types::Counter = arg_value.to_rust(); - let res = test::host::types::Counter::merge(&a, b); + let res = { + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[0usize], + ) + .unwrap(); + let a: test::host::types::Counter = arg_value.to_rust(); + let arg_value: Value = wasm_wave::from_str( + &::value_type(), + &args[1usize], + ) + .unwrap(); + let b: test::host::types::Counter = arg_value.to_rust(); + let res = test::host::types::Counter::merge(&a, b); + proxy::conversion::executor::host_drops(true); + res + }; + proxy::conversion::executor::host_drops(false); let wave_res = wasm_wave::to_string(&res.to_value()) .unwrap(); proxy::recorder::replay::check( @@ -186,16 +260,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { _ => unreachable!(), } } - event => unreachable!("the host can't do {event:?} in a p2 trace"), + event => unreachable!("`start` doesn't perform {event:?}"), } + proxy::conversion::executor::host_drops(true); SCOPED_ALLOC .with(|alloc| { alloc.borrow_mut().clear(); }); + proxy::conversion::executor::host_drops(false); } join_spawned().await; proxy::conversion::executor::stop(); __executor.await; + proxy::recorder::replay::finish(); } } impl ValueTyped for test::guest::api::Session { @@ -409,13 +486,19 @@ impl<'a> ValueTyped for &'a test::host::types::Counter { } impl ToValue for test::host::types::Counter { fn to_value(&self) -> Value { - let label = format!("{}-{}", "counter", self.handle()); + let label = format!( + "{}-{}", "counter", + proxy::conversion::conversion::get_handle_mock_test_host_types_counter(self) + ); Value::make_handle(label.into()) } } impl<'a> ToValue for &'a test::host::types::Counter { fn to_value(&self) -> Value { - let label = format!("borrow-{}-{}", "counter", self.handle()); + let label = format!( + "borrow-{}-{}", "counter", + proxy::conversion::conversion::get_handle_mock_test_host_types_counter(self) + ); Value::make_handle(label.into()) } } diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index 5fbb9e0..e30dcbc 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -11,6 +11,11 @@ impl exports::proxy::conversion::conversion::Guest for Stub { name: "counter".to_string(), }) } + fn get_handle_mock_test_host_types_counter( + x: exports::proxy::conversion::conversion::TestHostTypesCounterBorrow<'_>, + ) -> u32 { + x.get::().handle + } } impl exports::proxy::conversion::executor::Guest for Stub { async fn run() -> () { @@ -22,6 +27,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + async fn wait_until_step(step: u32) -> () { + wait_until_step(step).await + } fn host_drops(dropping: bool) -> () { MOVING.with(|moving| moving.set(dropping)) } @@ -568,6 +576,21 @@ const TRACE_TABLE: Table = Table::Imports; fn trace_at() -> u32 { current_step() } +/// The value of the `return` that answers an async call: at once, if the trace has +/// the call RETURNED, or, if STARTED, in the step of its `return`, which the mock +/// waits for. +async fn trace_answer(id: TaskId, answers: Vec) -> Option { + if !answers.iter().any(|line| matches!(line.event, Event::Started(_))) { + return trace_return_value(answers); + } + let line = proxy::recorder::replay::take_host_line(&LineKey::Task(id)) + .expect("the trace doesn't return the call"); + let Event::Return(ret) = line.event else { + panic!("the trace answers the call with {:?}", line.event); + }; + wait_until_step(line.at.expect("a STARTED call returns in a step")).await; + ret.value +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; @@ -597,38 +620,67 @@ fn current_step() -> u32 { STEP.get() } /// Waits, from any task, until the executor has run `runs` more times, and returns the step of -/// the last run. A job counts them, waking the executor itself in between. +/// the last run. #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is +/// already there, e.g. replay's for a host line recorded in that step. +/// +/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() == step { + return; + } + wait_for_run(move || { + let now = current_step(); + assert!( + now <= step, "replay reached step {now} before the run in step {step}" + ); + now == step + }) + .await; +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, } - let done = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_done = done.clone(); - let mut left = runs; + let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); + let job_shared = shared.clone(); spawn( std::future::poll_fn(move |_| { - if left == 0 { - let mut done = job_done.borrow_mut(); - done.step = Some(current_step()); - if let Some(waker) = done.waker.take() { + if done() { + let mut shared = job_shared.borrow_mut(); + shared.step = Some(current_step()); + if let Some(waker) = shared.waker.take() { waker.wake(); } return std::task::Poll::Ready(()); } - left -= 1; WAKE_AGAIN.set(true); std::task::Poll::Pending }), ); std::future::poll_fn(move |cx| { - let mut done = done.borrow_mut(); - match done.step { + let mut shared = shared.borrow_mut(); + match shared.step { Some(step) => std::task::Poll::Ready(step), None => { - done.waker = Some(cx.waker().clone()); + shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending } } diff --git a/tests/snapshots/replay/wit/deps/conversion/conversion.wit b/tests/snapshots/replay/wit/deps/conversion/conversion.wit index 062c419..81d6bc3 100644 --- a/tests/snapshots/replay/wit/deps/conversion/conversion.wit +++ b/tests/snapshots/replay/wit/deps/conversion/conversion.wit @@ -2,4 +2,5 @@ package proxy:conversion; interface conversion { use test:host/types@0.1.0.{counter as test-host-types-counter}; get-mock-test-host-types-counter: func(handle: u32) -> test-host-types-counter; + get-handle-mock-test-host-types-counter: func(x: borrow) -> u32; } From 56e56b435b6040743ad84c397ddd96d4d8eaea8e Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Wed, 4 Nov 2026 20:19:19 -0800 Subject: [PATCH 14/25] local ToRust trait --- src/codegen/replay.rs | 6 +- src/traits/wave.rs | 166 ++++++++++++---- tests/snapshots/dialog/record_exports/lib.rs | 139 ++++++++++---- tests/snapshots/dialog/record_imports/lib.rs | 125 +++++++++--- tests/snapshots/fuzz/record_exports/lib.rs | 6 +- tests/snapshots/fuzz/record_imports/lib.rs | 6 +- .../snapshots/p3-dialog/record_exports/lib.rs | 98 ++++++++-- .../snapshots/p3-dialog/record_imports/lib.rs | 96 ++++++++-- tests/snapshots/p3-fuzz/record_exports/lib.rs | 6 +- tests/snapshots/p3-fuzz/record_imports/lib.rs | 6 +- tests/snapshots/replay/record_exports/lib.rs | 181 +++++++++++++----- tests/snapshots/replay/record_imports/lib.rs | 139 ++++++++++---- 12 files changed, 761 insertions(+), 213 deletions(-) diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 0ace9b1..888ad97 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -32,9 +32,9 @@ impl State { let replay_import = if let Some(ret_ty) = ret_ty { let to_rust = match wit_func.constructor_resource() { Some(name) => { - quote! { MockedResource { name: #name.to_string(), ..ret.to_rust() } } + quote! { MockedResource { name: #name.to_string(), ..FromWave::from_wave(&ret) } } } - None => quote! { ret.to_rust() }, + None => quote! { FromWave::from_wave(&ret) }, }; quote! { #check_call @@ -178,7 +178,7 @@ impl State { let res = { #( let arg_value: Value = wasm_wave::from_str(&<#arg_tys as ValueTyped>::value_type(), &#arg_idx).unwrap(); - let #arg_names: #arg_tys = arg_value.to_rust(); + let #arg_names: #arg_tys = FromWave::from_wave(&arg_value); )* let res = #call; proxy::conversion::executor::host_drops(true); diff --git a/src/traits/wave.rs b/src/traits/wave.rs index 2d5f4de..5af0cfc 100644 --- a/src/traits/wave.rs +++ b/src/traits/wave.rs @@ -79,9 +79,9 @@ impl Trait for WaveTrait { if self.to_rust { let mock = new_mock(wit, quote! { handle }); res.push(parse_quote! { - impl ToRust<#resource_path> for Value { - fn to_rust(&self) -> #resource_path { - let label = self.unwrap_handle(); + impl FromWave for #resource_path { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", #wit_name)) .and_then(|s| s.parse::().ok()) @@ -91,9 +91,9 @@ impl Trait for WaveTrait { } }); res.push(parse_quote! { - impl<'a> ToRust<&'a #resource_path> for Value { - fn to_rust(&self) -> &'a #resource_path { - let label = self.unwrap_handle(); + impl<'a> FromWave for &'a #resource_path { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", #wit_name)) .and_then(|s| s.parse::().ok()) @@ -157,9 +157,9 @@ impl Trait for WaveTrait { } if self.to_rust && self.has_replay_table { res.push(parse_quote! { - impl ToRust<#resource_path> for Value { - fn to_rust(&self) -> #resource_path { - let label = self.unwrap_handle(); + impl FromWave for #resource_path { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let expect_handle = label .strip_prefix(&format!("{}-", #wit_name)) .and_then(|s| s.parse::().ok()) @@ -177,8 +177,8 @@ impl Trait for WaveTrait { } }); res.push(parse_quote! { - impl<'a> ToRust<#borrow_path> for Value { - fn to_rust(&self) -> #borrow_path { + impl<'a> FromWave for #borrow_path { + fn from_wave(_value: &Value) -> Self { unreachable!() } } @@ -236,11 +236,12 @@ impl Trait for WaveTrait { } if self.to_rust { res.push(parse_quote! { - impl #impl_generics ToRust<#struct_name #ty_generics> for Value #where_clause { - fn to_rust(&self) -> #struct_name #ty_generics { - let fields: std::collections::BTreeMap<_, _> = self.unwrap_record().collect(); + impl #impl_generics FromWave for #struct_name #ty_generics #where_clause { + #[allow(unused_variables)] + fn from_wave(value: &Value) -> Self { + let fields: std::collections::BTreeMap<_, _> = value.unwrap_record().collect(); #struct_name { - #(#field_names: fields[#wit_names].to_rust()),* + #(#field_names: FromWave::from_wave(&fields[#wit_names])),* } } } @@ -304,15 +305,15 @@ impl Trait for WaveTrait { (ref case, None) if case == #wit_name => #enum_name::#tag }, syn::Fields::Unnamed(_) => quote! { - (ref case, Some(val)) if case == #wit_name => #enum_name::#tag(val.to_rust()) + (ref case, Some(val)) if case == #wit_name => #enum_name::#tag(FromWave::from_wave(&val)) }, syn::Fields::Named(_) => unreachable!(), } }); res.push(parse_quote! { - impl #impl_generics ToRust<#enum_name #ty_generics> for Value #where_clause { - fn to_rust(&self) -> #enum_name #ty_generics { - match self.unwrap_variant() { + impl #impl_generics FromWave for #enum_name #ty_generics #where_clause { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { #(#arms),*, _ => unreachable!(), } @@ -366,9 +367,9 @@ impl Trait for WaveTrait { } }); res.push(parse_quote! { - impl ToRust<#flag_path> for Value { - fn to_rust(&self) -> #flag_path { - let flags: Vec<_> = self.unwrap_flags().map(|f| f.into_owned()).collect(); + impl FromWave for #flag_path { + fn from_wave(value: &Value) -> Self { + let flags: Vec<_> = value.unwrap_flags().map(|f| f.into_owned()).collect(); let mut res = #flag_path::empty(); #(#set_flags)* res @@ -446,22 +447,116 @@ impl Trait for WaveTrait { Value::make_handle(label.into()) } } - // Only called by constructor - impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { - handle, - name, + } + } else { + quote! {} + }; + let mocked_from_wave = if self.has_replay_table { + quote! { + // Only called by constructor + impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { + handle, + name, + } + } + } + } + } else { + quote! {} + }; + let from_wave = if self.to_rust { + quote! { + /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a + /// local trait, so that it can be implemented for wit-bindgen's streams, futures and + /// error-contexts, as `Fuzz` is for `Arbitrary`. + trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } + } + macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $( + impl FromWave for $t { + fn from_wave(value: &Value) -> Self { + value.to_rust() + } + } + )* + }; + } + impl_from_wave_primitive!(bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String); + impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} + } + impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } + } + impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } + } + impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), } } } + macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl<$($T: FromWave),+> FromWave for ($($T,)+) { + fn from_wave(value: &Value) -> Self { + let mut items = value.unwrap_tuple(); + ($($T::from_wave(&items.next().unwrap()),)+) + } + } + }; + } + impl_from_wave_tuple!(T1); + impl_from_wave_tuple!(T1, T2); + impl_from_wave_tuple!(T1, T2, T3); + impl_from_wave_tuple!(T1, T2, T3, T4); + impl_from_wave_tuple!(T1, T2, T3, T4, T5); + impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); + impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); + impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); + impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); + // The host's ends of streams and futures, which replay makes from the trace (phase + // 3b-2 of `docs/p3-record-replay.md`). + impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } + } + impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } + } + // The trace doesn't have an error-context's message, only its label. + impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } + } + #mocked_from_wave } } else { quote! {} @@ -470,6 +565,7 @@ impl Trait for WaveTrait { #[allow(unused_imports)] use wasm_wave::{wasm::WasmValue, value::{Value, Type, convert::{ToRust, ToValue, ValueTyped}}}; #mocked_resource + #from_wave }; ast.items } diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index be387ea..a16f869 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -348,9 +348,9 @@ impl<'a> ToValue for &'a test::guest::api::Session { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> test::guest::api::Session { - let label = self.unwrap_handle(); +impl FromWave for test::guest::api::Session { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "session")) .and_then(|s| s.parse::().ok()) @@ -364,9 +364,9 @@ impl ToRust for Value { } } } -impl<'a> ToRust<&'a test::guest::api::Session> for Value { - fn to_rust(&self) -> &'a test::guest::api::Session { - let label = self.unwrap_handle(); +impl<'a> FromWave for &'a test::guest::api::Session { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "session")) .and_then(|s| s.parse::().ok()) @@ -408,13 +408,14 @@ impl ToValue for test::host::types::Point { Value::make_record(&ty, fields).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Point { - let fields: std::collections::BTreeMap<_, _> = self.unwrap_record().collect(); +impl FromWave for test::host::types::Point { + #[allow(unused_variables)] + fn from_wave(value: &Value) -> Self { + let fields: std::collections::BTreeMap<_, _> = value.unwrap_record().collect(); test::host::types::Point { - x: fields["x"].to_rust(), - y: fields["y"].to_rust(), - label: fields["label"].to_rust(), + x: FromWave::from_wave(&fields["x"]), + y: FromWave::from_wave(&fields["y"]), + label: FromWave::from_wave(&fields["label"]), } } } @@ -443,17 +444,17 @@ impl ToValue for test::host::types::Shape { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Shape { - match self.unwrap_variant() { +impl FromWave for test::host::types::Shape { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, Some(val)) if case == "circle" => { - test::host::types::Shape::Circle(val.to_rust()) + test::host::types::Shape::Circle(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "rect" => { - test::host::types::Shape::Rect(val.to_rust()) + test::host::types::Shape::Rect(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "HTTP-error" => { - test::host::types::Shape::HttpError(val.to_rust()) + test::host::types::Shape::HttpError(FromWave::from_wave(&val)) } (ref case, None) if case == "empty" => test::host::types::Shape::Empty, _ => unreachable!(), @@ -478,9 +479,9 @@ impl ToValue for test::host::types::Color { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Color { - match self.unwrap_variant() { +impl FromWave for test::host::types::Color { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, None) if case == "red" => test::host::types::Color::Red, (ref case, None) if case == "green" => test::host::types::Color::Green, (ref case, None) if case == "DNS-blue" => test::host::types::Color::DnsBlue, @@ -509,9 +510,9 @@ impl ToValue for test::host::types::Perms { Value::make_flags(&ty, flags).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Perms { - let flags: Vec<_> = self.unwrap_flags().map(|f| f.into_owned()).collect(); +impl FromWave for test::host::types::Perms { + fn from_wave(value: &Value) -> Self { + let flags: Vec<_> = value.unwrap_flags().map(|f| f.into_owned()).collect(); let mut res = test::host::types::Perms::empty(); if flags.iter().any(|flag| flag == "read") { res |= test::host::types::Perms::READ; @@ -553,9 +554,9 @@ impl<'a> ToValue for &'a test::host::types::Counter { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Counter { - let label = self.unwrap_handle(); +impl FromWave for test::host::types::Counter { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "counter")) .and_then(|s| s.parse::().ok()) @@ -563,9 +564,9 @@ impl ToRust for Value { proxy::conversion::conversion::get_mock_test_host_types_counter(handle) } } -impl<'a> ToRust<&'a test::host::types::Counter> for Value { - fn to_rust(&self) -> &'a test::host::types::Counter { - let label = self.unwrap_handle(); +impl<'a> FromWave for &'a test::host::types::Counter { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "counter")) .and_then(|s| s.parse::().ok()) @@ -646,9 +647,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { @@ -658,6 +659,78 @@ impl ToRust for Value { MockedResource { handle, name } } } +/// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a +/// local trait, so that it can be implemented for wit-bindgen's streams, futures and +/// error-contexts, as `Fuzz` is for `Arbitrary`. +trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } +} +macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $(impl FromWave for $t { fn from_wave(value : & Value) -> Self { value.to_rust() + } })* + }; +} +impl_from_wave_primitive!( + bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String +); +impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} +} +impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } +} +impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } +} +impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), + } + } +} +macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl < $($T : FromWave),+ > FromWave for ($($T,)+) { fn from_wave(value : & + Value) -> Self { let mut items = value.unwrap_tuple(); ($($T ::from_wave(& items + .next().unwrap()),)+) } } + }; +} +impl_from_wave_tuple!(T1); +impl_from_wave_tuple!(T1, T2); +impl_from_wave_tuple!(T1, T2, T3); +impl_from_wave_tuple!(T1, T2, T3, T4); +impl_from_wave_tuple!(T1, T2, T3, T4, T5); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } +} +impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } +} +impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } +} impl Dialog for test::guest::api::Session { fn read_value(_dep: u32) -> Self { { diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index cf8b809..e8298f9 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -223,13 +223,14 @@ impl ToValue for exports::test::host::types::Point { Value::make_record(&ty, fields).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Point { - let fields: std::collections::BTreeMap<_, _> = self.unwrap_record().collect(); +impl FromWave for exports::test::host::types::Point { + #[allow(unused_variables)] + fn from_wave(value: &Value) -> Self { + let fields: std::collections::BTreeMap<_, _> = value.unwrap_record().collect(); exports::test::host::types::Point { - x: fields["x"].to_rust(), - y: fields["y"].to_rust(), - label: fields["label"].to_rust(), + x: FromWave::from_wave(&fields["x"]), + y: FromWave::from_wave(&fields["y"]), + label: FromWave::from_wave(&fields["label"]), } } } @@ -259,17 +260,17 @@ impl ToValue for exports::test::host::types::Shape { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Shape { - match self.unwrap_variant() { +impl FromWave for exports::test::host::types::Shape { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, Some(val)) if case == "circle" => { - exports::test::host::types::Shape::Circle(val.to_rust()) + exports::test::host::types::Shape::Circle(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "rect" => { - exports::test::host::types::Shape::Rect(val.to_rust()) + exports::test::host::types::Shape::Rect(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "HTTP-error" => { - exports::test::host::types::Shape::HttpError(val.to_rust()) + exports::test::host::types::Shape::HttpError(FromWave::from_wave(&val)) } (ref case, None) if case == "empty" => { exports::test::host::types::Shape::Empty @@ -296,9 +297,9 @@ impl ToValue for exports::test::host::types::Color { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Color { - match self.unwrap_variant() { +impl FromWave for exports::test::host::types::Color { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, None) if case == "red" => exports::test::host::types::Color::Red, (ref case, None) if case == "green" => { exports::test::host::types::Color::Green @@ -331,9 +332,9 @@ impl ToValue for exports::test::host::types::Perms { Value::make_flags(&ty, flags).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Perms { - let flags: Vec<_> = self.unwrap_flags().map(|f| f.into_owned()).collect(); +impl FromWave for exports::test::host::types::Perms { + fn from_wave(value: &Value) -> Self { + let flags: Vec<_> = value.unwrap_flags().map(|f| f.into_owned()).collect(); let mut res = exports::test::host::types::Perms::empty(); if flags.iter().any(|flag| flag == "read") { res |= exports::test::host::types::Perms::READ; @@ -371,9 +372,9 @@ impl<'a> ToValue for exports::test::host::types::CounterBorrow<'a> { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Counter { - let label = self.unwrap_handle(); +impl FromWave for exports::test::host::types::Counter { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let expect_handle = label .strip_prefix(&format!("{}-", "counter")) .and_then(|s| s.parse::().ok()) @@ -385,8 +386,8 @@ impl ToRust for Value { handle } } -impl<'a> ToRust> for Value { - fn to_rust(&self) -> exports::test::host::types::CounterBorrow<'a> { +impl<'a> FromWave for exports::test::host::types::CounterBorrow<'a> { + fn from_wave(_value: &Value) -> Self { unreachable!() } } @@ -454,9 +455,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { @@ -466,6 +467,78 @@ impl ToRust for Value { MockedResource { handle, name } } } +/// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a +/// local trait, so that it can be implemented for wit-bindgen's streams, futures and +/// error-contexts, as `Fuzz` is for `Arbitrary`. +trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } +} +macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $(impl FromWave for $t { fn from_wave(value : & Value) -> Self { value.to_rust() + } })* + }; +} +impl_from_wave_primitive!( + bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String +); +impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} +} +impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } +} +impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } +} +impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), + } + } +} +macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl < $($T : FromWave),+ > FromWave for ($($T,)+) { fn from_wave(value : & + Value) -> Self { let mut items = value.unwrap_tuple(); ($($T ::from_wave(& items + .next().unwrap()),)+) } } + }; +} +impl_from_wave_tuple!(T1); +impl_from_wave_tuple!(T1, T2); +impl_from_wave_tuple!(T1, T2, T3); +impl_from_wave_tuple!(T1, T2, T3, T4); +impl_from_wave_tuple!(T1, T2, T3, T4, T5); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } +} +impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } +} +impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } +} impl Dialog for exports::test::host::types::Point { fn read_value(dep: u32) -> Self { proxy::util::dialog::print( diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index 94e30ce..a498d22 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -601,9 +601,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 5b31e40..289b40d 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -518,9 +518,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index 8df1ca9..04aca03 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -336,12 +336,12 @@ impl ToValue for test::p3::host::ErrorCode { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::p3::host::ErrorCode { - match self.unwrap_variant() { +impl FromWave for test::p3::host::ErrorCode { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, None) if case == "timeout" => test::p3::host::ErrorCode::Timeout, (ref case, Some(val)) if case == "other" => { - test::p3::host::ErrorCode::Other(val.to_rust()) + test::p3::host::ErrorCode::Other(FromWave::from_wave(&val)) } _ => unreachable!(), } @@ -375,9 +375,9 @@ impl<'a> ToValue for &'a test::p3::host::Body { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> test::p3::host::Body { - let label = self.unwrap_handle(); +impl FromWave for test::p3::host::Body { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "body")) .and_then(|s| s.parse::().ok()) @@ -385,9 +385,9 @@ impl ToRust for Value { proxy::conversion::conversion::get_mock_test_p3_host_body(handle) } } -impl<'a> ToRust<&'a test::p3::host::Body> for Value { - fn to_rust(&self) -> &'a test::p3::host::Body { - let label = self.unwrap_handle(); +impl<'a> FromWave for &'a test::p3::host::Body { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "body")) .and_then(|s| s.parse::().ok()) @@ -466,9 +466,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { @@ -478,6 +478,78 @@ impl ToRust for Value { MockedResource { handle, name } } } +/// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a +/// local trait, so that it can be implemented for wit-bindgen's streams, futures and +/// error-contexts, as `Fuzz` is for `Arbitrary`. +trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } +} +macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $(impl FromWave for $t { fn from_wave(value : & Value) -> Self { value.to_rust() + } })* + }; +} +impl_from_wave_primitive!( + bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String +); +impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} +} +impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } +} +impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } +} +impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), + } + } +} +macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl < $($T : FromWave),+ > FromWave for ($($T,)+) { fn from_wave(value : & + Value) -> Self { let mut items = value.unwrap_tuple(); ($($T ::from_wave(& items + .next().unwrap()),)+) } } + }; +} +impl_from_wave_tuple!(T1); +impl_from_wave_tuple!(T1, T2); +impl_from_wave_tuple!(T1, T2, T3); +impl_from_wave_tuple!(T1, T2, T3, T4); +impl_from_wave_tuple!(T1, T2, T3, T4, T5); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } +} +impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } +} +impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } +} impl Dialog for test::p3::host::ErrorCode { fn read_value(dep: u32) -> Self { let idx = proxy::util::dialog::read_select( diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index dfb7cda..ecb5e42 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -191,14 +191,14 @@ impl ToValue for exports::test::p3::host::ErrorCode { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::p3::host::ErrorCode { - match self.unwrap_variant() { +impl FromWave for exports::test::p3::host::ErrorCode { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, None) if case == "timeout" => { exports::test::p3::host::ErrorCode::Timeout } (ref case, Some(val)) if case == "other" => { - exports::test::p3::host::ErrorCode::Other(val.to_rust()) + exports::test::p3::host::ErrorCode::Other(FromWave::from_wave(&val)) } _ => unreachable!(), } @@ -228,9 +228,9 @@ impl<'a> ToValue for exports::test::p3::host::BodyBorrow<'a> { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::p3::host::Body { - let label = self.unwrap_handle(); +impl FromWave for exports::test::p3::host::Body { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let expect_handle = label .strip_prefix(&format!("{}-", "body")) .and_then(|s| s.parse::().ok()) @@ -242,8 +242,8 @@ impl ToRust for Value { handle } } -impl<'a> ToRust> for Value { - fn to_rust(&self) -> exports::test::p3::host::BodyBorrow<'a> { +impl<'a> FromWave for exports::test::p3::host::BodyBorrow<'a> { + fn from_wave(_value: &Value) -> Self { unreachable!() } } @@ -311,9 +311,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { @@ -323,6 +323,78 @@ impl ToRust for Value { MockedResource { handle, name } } } +/// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a +/// local trait, so that it can be implemented for wit-bindgen's streams, futures and +/// error-contexts, as `Fuzz` is for `Arbitrary`. +trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } +} +macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $(impl FromWave for $t { fn from_wave(value : & Value) -> Self { value.to_rust() + } })* + }; +} +impl_from_wave_primitive!( + bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String +); +impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} +} +impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } +} +impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } +} +impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), + } + } +} +macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl < $($T : FromWave),+ > FromWave for ($($T,)+) { fn from_wave(value : & + Value) -> Self { let mut items = value.unwrap_tuple(); ($($T ::from_wave(& items + .next().unwrap()),)+) } } + }; +} +impl_from_wave_tuple!(T1); +impl_from_wave_tuple!(T1, T2); +impl_from_wave_tuple!(T1, T2, T3); +impl_from_wave_tuple!(T1, T2, T3, T4); +impl_from_wave_tuple!(T1, T2, T3, T4, T5); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } +} +impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } +} +impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } +} impl Dialog for exports::test::p3::host::ErrorCode { fn read_value(dep: u32) -> Self { let idx = proxy::util::dialog::read_select( diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 4d59ac4..5cb0f60 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -510,9 +510,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 9e93eae..4379409 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -429,9 +429,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index a4883b0..bf850ce 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -45,13 +45,17 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let p: test::guest::api::Point = arg_value.to_rust(); + let p: test::guest::api::Point = FromWave::from_wave( + &arg_value, + ); let arg_value: Value = wasm_wave::from_str( &::value_type(), &args[1usize], ) .unwrap(); - let c: test::guest::api::Counter = arg_value.to_rust(); + let c: test::guest::api::Counter = FromWave::from_wave( + &arg_value, + ); let res = test::guest::api::run(&p, &c); proxy::conversion::executor::host_drops(true); res @@ -70,7 +74,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let s: test::guest::api::Shape = arg_value.to_rust(); + let s: test::guest::api::Shape = FromWave::from_wave( + &arg_value, + ); let res = test::guest::api::classify(&s); proxy::conversion::executor::host_drops(true); res @@ -89,7 +95,9 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let c: test::guest::api::Counter = arg_value.to_rust(); + let c: test::guest::api::Counter = FromWave::from_wave( + &arg_value, + ); let res = test::guest::api::begin(c); proxy::conversion::executor::host_drops(true); res @@ -109,7 +117,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let name: String = arg_value.to_rust(); + let name: String = FromWave::from_wave(&arg_value); let res = test::guest::api::Session::new(&name); proxy::conversion::executor::host_drops(true); res @@ -129,13 +137,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let x: i32 = arg_value.to_rust(); + let x: i32 = FromWave::from_wave(&arg_value); let arg_value: Value = wasm_wave::from_str( &::value_type(), &args[1usize], ) .unwrap(); - let y: i32 = arg_value.to_rust(); + let y: i32 = FromWave::from_wave(&arg_value); let res = test::host::types::make_point(x, y); proxy::conversion::executor::host_drops(true); res @@ -154,13 +162,17 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let p: test::host::types::Perms = arg_value.to_rust(); + let p: test::host::types::Perms = FromWave::from_wave( + &arg_value, + ); let arg_value: Value = wasm_wave::from_str( &::value_type(), &args[1usize], ) .unwrap(); - let c: test::host::types::Color = arg_value.to_rust(); + let c: test::host::types::Color = FromWave::from_wave( + &arg_value, + ); let res = test::host::types::check(p, c); proxy::conversion::executor::host_drops(true); res @@ -179,7 +191,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let name: String = arg_value.to_rust(); + let name: String = FromWave::from_wave(&arg_value); let res = test::host::types::open(&name); proxy::conversion::executor::host_drops(true); res @@ -199,7 +211,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let start: u32 = arg_value.to_rust(); + let start: u32 = FromWave::from_wave(&arg_value); let res = test::host::types::Counter::new(start); proxy::conversion::executor::host_drops(true); res @@ -219,13 +231,17 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &args[0usize], ) .unwrap(); - let a: test::host::types::Counter = arg_value.to_rust(); + let a: test::host::types::Counter = FromWave::from_wave( + &arg_value, + ); let arg_value: Value = wasm_wave::from_str( &::value_type(), &args[1usize], ) .unwrap(); - let b: test::host::types::Counter = arg_value.to_rust(); + let b: test::host::types::Counter = FromWave::from_wave( + &arg_value, + ); let res = test::host::types::Counter::merge(&a, b); proxy::conversion::executor::host_drops(true); res @@ -297,9 +313,9 @@ impl<'a> ToValue for &'a test::guest::api::Session { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> test::guest::api::Session { - let label = self.unwrap_handle(); +impl FromWave for test::guest::api::Session { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "session")) .and_then(|s| s.parse::().ok()) @@ -313,9 +329,9 @@ impl ToRust for Value { } } } -impl<'a> ToRust<&'a test::guest::api::Session> for Value { - fn to_rust(&self) -> &'a test::guest::api::Session { - let label = self.unwrap_handle(); +impl<'a> FromWave for &'a test::guest::api::Session { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "session")) .and_then(|s| s.parse::().ok()) @@ -357,13 +373,14 @@ impl ToValue for test::host::types::Point { Value::make_record(&ty, fields).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Point { - let fields: std::collections::BTreeMap<_, _> = self.unwrap_record().collect(); +impl FromWave for test::host::types::Point { + #[allow(unused_variables)] + fn from_wave(value: &Value) -> Self { + let fields: std::collections::BTreeMap<_, _> = value.unwrap_record().collect(); test::host::types::Point { - x: fields["x"].to_rust(), - y: fields["y"].to_rust(), - label: fields["label"].to_rust(), + x: FromWave::from_wave(&fields["x"]), + y: FromWave::from_wave(&fields["y"]), + label: FromWave::from_wave(&fields["label"]), } } } @@ -392,17 +409,17 @@ impl ToValue for test::host::types::Shape { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Shape { - match self.unwrap_variant() { +impl FromWave for test::host::types::Shape { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, Some(val)) if case == "circle" => { - test::host::types::Shape::Circle(val.to_rust()) + test::host::types::Shape::Circle(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "rect" => { - test::host::types::Shape::Rect(val.to_rust()) + test::host::types::Shape::Rect(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "HTTP-error" => { - test::host::types::Shape::HttpError(val.to_rust()) + test::host::types::Shape::HttpError(FromWave::from_wave(&val)) } (ref case, None) if case == "empty" => test::host::types::Shape::Empty, _ => unreachable!(), @@ -427,9 +444,9 @@ impl ToValue for test::host::types::Color { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Color { - match self.unwrap_variant() { +impl FromWave for test::host::types::Color { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, None) if case == "red" => test::host::types::Color::Red, (ref case, None) if case == "green" => test::host::types::Color::Green, (ref case, None) if case == "DNS-blue" => test::host::types::Color::DnsBlue, @@ -458,9 +475,9 @@ impl ToValue for test::host::types::Perms { Value::make_flags(&ty, flags).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Perms { - let flags: Vec<_> = self.unwrap_flags().map(|f| f.into_owned()).collect(); +impl FromWave for test::host::types::Perms { + fn from_wave(value: &Value) -> Self { + let flags: Vec<_> = value.unwrap_flags().map(|f| f.into_owned()).collect(); let mut res = test::host::types::Perms::empty(); if flags.iter().any(|flag| flag == "read") { res |= test::host::types::Perms::READ; @@ -502,9 +519,9 @@ impl<'a> ToValue for &'a test::host::types::Counter { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> test::host::types::Counter { - let label = self.unwrap_handle(); +impl FromWave for test::host::types::Counter { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "counter")) .and_then(|s| s.parse::().ok()) @@ -512,9 +529,9 @@ impl ToRust for Value { proxy::conversion::conversion::get_mock_test_host_types_counter(handle) } } -impl<'a> ToRust<&'a test::host::types::Counter> for Value { - fn to_rust(&self) -> &'a test::host::types::Counter { - let label = self.unwrap_handle(); +impl<'a> FromWave for &'a test::host::types::Counter { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "counter")) .and_then(|s| s.parse::().ok()) @@ -595,9 +612,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { @@ -607,6 +624,78 @@ impl ToRust for Value { MockedResource { handle, name } } } +/// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a +/// local trait, so that it can be implemented for wit-bindgen's streams, futures and +/// error-contexts, as `Fuzz` is for `Arbitrary`. +trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } +} +macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $(impl FromWave for $t { fn from_wave(value : & Value) -> Self { value.to_rust() + } })* + }; +} +impl_from_wave_primitive!( + bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String +); +impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} +} +impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } +} +impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } +} +impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), + } + } +} +macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl < $($T : FromWave),+ > FromWave for ($($T,)+) { fn from_wave(value : & + Value) -> Self { let mut items = value.unwrap_tuple(); ($($T ::from_wave(& items + .next().unwrap()),)+) } } + }; +} +impl_from_wave_tuple!(T1); +impl_from_wave_tuple!(T1, T2); +impl_from_wave_tuple!(T1, T2, T3); +impl_from_wave_tuple!(T1, T2, T3, T4); +impl_from_wave_tuple!(T1, T2, T3, T4, T5); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } +} +impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } +} +impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } +} use bindings::proxy::recorder::types::*; thread_local! { static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index e30dcbc..fda96d2 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -54,7 +54,7 @@ impl exports::test::host::types::Guest for Stub { &wave.unwrap(), ) .unwrap(); - ret.to_rust() + FromWave::from_wave(&ret) } fn check( p: exports::test::host::types::Perms, @@ -80,7 +80,7 @@ impl exports::test::host::types::Guest for Stub { &wave.unwrap(), ) .unwrap(); - ret.to_rust() + FromWave::from_wave(&ret) } fn open( name: String, @@ -104,7 +104,7 @@ impl exports::test::host::types::Guest for Stub { &wave.unwrap(), ) .unwrap(); - ret.to_rust() + FromWave::from_wave(&ret) } } impl exports::test::host::types::GuestCounter for MockedResource { @@ -127,7 +127,7 @@ impl exports::test::host::types::GuestCounter for MockedResource { .unwrap(); MockedResource { name: "counter".to_string(), - ..ret.to_rust() + ..FromWave::from_wave(&ret) } } fn get(&self) -> u32 { @@ -148,7 +148,7 @@ impl exports::test::host::types::GuestCounter for MockedResource { &wave.unwrap(), ) .unwrap(); - ret.to_rust() + FromWave::from_wave(&ret) } fn add(&self, n: u32) -> () { let mut __params: Vec = vec![ @@ -188,7 +188,7 @@ impl exports::test::host::types::GuestCounter for MockedResource { &wave.unwrap(), ) .unwrap(); - ret.to_rust() + FromWave::from_wave(&ret) }; MOVING.with(|moving| moving.set(true)); drop((b,)); @@ -241,7 +241,7 @@ impl exports::test::log0_2_0::sink::Guest for Stub { &wave.unwrap(), ) .unwrap(); - ret.to_rust() + FromWave::from_wave(&ret) } } impl ValueTyped for exports::test::host::types::Point { @@ -265,13 +265,14 @@ impl ToValue for exports::test::host::types::Point { Value::make_record(&ty, fields).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Point { - let fields: std::collections::BTreeMap<_, _> = self.unwrap_record().collect(); +impl FromWave for exports::test::host::types::Point { + #[allow(unused_variables)] + fn from_wave(value: &Value) -> Self { + let fields: std::collections::BTreeMap<_, _> = value.unwrap_record().collect(); exports::test::host::types::Point { - x: fields["x"].to_rust(), - y: fields["y"].to_rust(), - label: fields["label"].to_rust(), + x: FromWave::from_wave(&fields["x"]), + y: FromWave::from_wave(&fields["y"]), + label: FromWave::from_wave(&fields["label"]), } } } @@ -301,17 +302,17 @@ impl ToValue for exports::test::host::types::Shape { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Shape { - match self.unwrap_variant() { +impl FromWave for exports::test::host::types::Shape { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, Some(val)) if case == "circle" => { - exports::test::host::types::Shape::Circle(val.to_rust()) + exports::test::host::types::Shape::Circle(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "rect" => { - exports::test::host::types::Shape::Rect(val.to_rust()) + exports::test::host::types::Shape::Rect(FromWave::from_wave(&val)) } (ref case, Some(val)) if case == "HTTP-error" => { - exports::test::host::types::Shape::HttpError(val.to_rust()) + exports::test::host::types::Shape::HttpError(FromWave::from_wave(&val)) } (ref case, None) if case == "empty" => { exports::test::host::types::Shape::Empty @@ -338,9 +339,9 @@ impl ToValue for exports::test::host::types::Color { .unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Color { - match self.unwrap_variant() { +impl FromWave for exports::test::host::types::Color { + fn from_wave(value: &Value) -> Self { + match value.unwrap_variant() { (ref case, None) if case == "red" => exports::test::host::types::Color::Red, (ref case, None) if case == "green" => { exports::test::host::types::Color::Green @@ -373,9 +374,9 @@ impl ToValue for exports::test::host::types::Perms { Value::make_flags(&ty, flags).unwrap() } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Perms { - let flags: Vec<_> = self.unwrap_flags().map(|f| f.into_owned()).collect(); +impl FromWave for exports::test::host::types::Perms { + fn from_wave(value: &Value) -> Self { + let flags: Vec<_> = value.unwrap_flags().map(|f| f.into_owned()).collect(); let mut res = exports::test::host::types::Perms::empty(); if flags.iter().any(|flag| flag == "read") { res |= exports::test::host::types::Perms::READ; @@ -413,9 +414,9 @@ impl<'a> ToValue for exports::test::host::types::CounterBorrow<'a> { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> exports::test::host::types::Counter { - let label = self.unwrap_handle(); +impl FromWave for exports::test::host::types::Counter { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let expect_handle = label .strip_prefix(&format!("{}-", "counter")) .and_then(|s| s.parse::().ok()) @@ -427,8 +428,8 @@ impl ToRust for Value { handle } } -impl<'a> ToRust> for Value { - fn to_rust(&self) -> exports::test::host::types::CounterBorrow<'a> { +impl<'a> FromWave for exports::test::host::types::CounterBorrow<'a> { + fn from_wave(_value: &Value) -> Self { unreachable!() } } @@ -496,9 +497,9 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl ToRust for Value { - fn to_rust(&self) -> MockedResource { - let label = self.unwrap_handle(); +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); let (name, handle) = label .rsplit_once('-') .and_then(|(name, handle_str)| { @@ -508,6 +509,78 @@ impl ToRust for Value { MockedResource { handle, name } } } +/// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a +/// local trait, so that it can be implemented for wit-bindgen's streams, futures and +/// error-contexts, as `Fuzz` is for `Arbitrary`. +trait FromWave: Sized { + fn from_wave(value: &Value) -> Self; + /// From the payload of a result's or a variant's case, which `()` has none of. + fn from_payload(value: Option<&Value>) -> Self { + Self::from_wave(value.expect("the case has a payload")) + } +} +macro_rules! impl_from_wave_primitive { + ($($t:ty),*) => { + $(impl FromWave for $t { fn from_wave(value : & Value) -> Self { value.to_rust() + } })* + }; +} +impl_from_wave_primitive!( + bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, char, String +); +impl FromWave for () { + fn from_wave(_value: &Value) -> Self {} + fn from_payload(_value: Option<&Value>) -> Self {} +} +impl FromWave for Vec { + fn from_wave(value: &Value) -> Self { + value.unwrap_list().map(|x| T::from_wave(&x)).collect() + } +} +impl FromWave for Option { + fn from_wave(value: &Value) -> Self { + value.unwrap_option().map(|x| T::from_wave(&x)) + } +} +impl FromWave for core::result::Result { + fn from_wave(value: &Value) -> Self { + match value.unwrap_result() { + Ok(x) => Ok(T::from_payload(x.as_deref())), + Err(x) => Err(E::from_payload(x.as_deref())), + } + } +} +macro_rules! impl_from_wave_tuple { + ($($T:ident),+) => { + impl < $($T : FromWave),+ > FromWave for ($($T,)+) { fn from_wave(value : & + Value) -> Self { let mut items = value.unwrap_tuple(); ($($T ::from_wave(& items + .next().unwrap()),)+) } } + }; +} +impl_from_wave_tuple!(T1); +impl_from_wave_tuple!(T1, T2); +impl_from_wave_tuple!(T1, T2, T3); +impl_from_wave_tuple!(T1, T2, T3, T4); +impl_from_wave_tuple!(T1, T2, T3, T4, T5); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +impl_from_wave_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9); +impl FromWave for wit_bindgen::StreamReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a stream") + } +} +impl FromWave for wit_bindgen::FutureReader { + fn from_wave(_value: &Value) -> Self { + unimplemented!("replaying a future") + } +} +impl FromWave for wit_bindgen::rt::async_support::ErrorContext { + fn from_wave(_value: &Value) -> Self { + Self::new("") + } +} use bindings::proxy::recorder::types::*; thread_local! { static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; From 6a011b01d3f1df6549ced9e520d9e143a292082c Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Thu, 5 Nov 2026 11:37:43 -0800 Subject: [PATCH 15/25] replay checks for label upto renaming --- Makefile | 16 +- assets/executor.rs | 16 +- assets/executor.wit | 5 +- assets/recorder.one_byte_page.wasm | Bin 308856 -> 336823 bytes assets/recorder.wasm | Bin 308860 -> 336827 bytes crates/trace/src/lib.rs | 121 ++++++- crates/trace/src/renaming.rs | 306 ++++++++++++++++++ docs/p3-record-replay.md | 41 ++- src/codegen/dialog.rs | 22 +- src/codegen/fuzz.rs | 83 ++--- src/codegen/mod.rs | 94 +++++- src/codegen/replay.rs | 78 +---- src/instrument.rs | 4 +- tests/snapshots/dialog/record_exports/lib.rs | 114 +++++-- tests/snapshots/dialog/record_imports/lib.rs | 45 ++- tests/snapshots/fuzz/record_exports/lib.rs | 130 ++++---- tests/snapshots/fuzz/record_imports/lib.rs | 33 +- .../snapshots/p3-dialog/record_exports/lib.rs | 93 ++++-- .../snapshots/p3-dialog/record_imports/lib.rs | 45 ++- tests/snapshots/p3-fuzz/record_exports/lib.rs | 136 ++++---- tests/snapshots/p3-fuzz/record_imports/lib.rs | 33 +- tests/snapshots/replay/record_exports/lib.rs | 183 ++++++----- tests/snapshots/replay/record_imports/lib.rs | 52 +-- 23 files changed, 1109 insertions(+), 541 deletions(-) create mode 100644 crates/trace/src/renaming.rs diff --git a/Makefile b/Makefile index 9aacdcd..eb90ee7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-fuzz test-dialog run-fuzz run-record run-dialog run-viceroy +.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-fuzz-replay test-fuzz test-dialog run-fuzz run-record run-dialog run-viceroy # `make test ONE_BYTE_PAGE=1` tests with 1-byte-page memories ifeq ($(ONE_BYTE_PAGE),1) @@ -20,7 +20,7 @@ build-components: cp target/one-byte-page/wasm32-wasip2/release/debug.wasm assets/debug.one_byte_page.wasm cp target/one-byte-page/wasm32-wasip2/release/recorder.wasm assets/recorder.one_byte_page.wasm -test: test-cargo test-fuzz test-record test-fixture test-dialog +test: test-cargo test-fuzz test-record test-fixture test-fuzz-replay test-dialog # Snapshot tests of the generated sources test-cargo: @@ -73,6 +73,18 @@ test-fixture: $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm target/release/proxy-component run composed.wasm --invoke 'start()' --trace $(FIXTURE_OUT)/trace.out +# Fuzzes the main component of tests/fixtures/proxy-run/guest, and replays each trace it writes +test-fuzz-replay: + cargo build --manifest-path $(FIXTURE)/guest/Cargo.toml --target wasm32-wasip2 --release --target-dir $(FIXTURE_OUT) + $(INSTRUMENT) -m fuzz $(FIXTURE_WASM)/proxy_guest.wasm + cp composed.wasm $(FIXTURE_OUT)/fuzz.wasm + $(INSTRUMENT) -m replay $(FIXTURE_WASM)/proxy_guest.wasm + cp composed.wasm $(FIXTURE_OUT)/replay.wasm + for i in 1 2 3 4 5 6 7 8 9 10; do \ + $(WASMTIME) --invoke 'start()' $(FIXTURE_OUT)/fuzz.wasm > $(FIXTURE_OUT)/fuzz-trace.out || exit 1; \ + $(WASMTIME) --invoke 'start()' $(FIXTURE_OUT)/replay.wasm < $(FIXTURE_OUT)/fuzz-trace.out > /dev/null || exit 1; \ + done + test-dialog: rm tests/composed.wasm || true for wasm in tests/*.wasm; do \ diff --git a/assets/executor.rs b/assets/executor.rs index 53bfb8d..e3dde3d 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -87,21 +87,15 @@ async fn wait_for_runs(runs: u32) -> u32 { }) .await } -/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is -/// already there, e.g. replay's for a host line recorded in that step. -/// -/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. #[allow(dead_code)] async fn wait_until_step(step: u32) { - if current_step() == step { + if current_step() >= step { return; } - wait_for_run(move || { - let now = current_step(); - assert!(now <= step, "replay reached step {now} before the run in step {step}"); - now == step - }) - .await; + wait_for_run(move || current_step() >= step).await; } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. diff --git a/assets/executor.wit b/assets/executor.wit index 80c3ec4..614b1e8 100644 --- a/assets/executor.wit +++ b/assets/executor.wit @@ -11,8 +11,11 @@ interface executor { // Counts a step that isn't a run of the executor, e.g. the arrival of an export call, which the // exports component makes, and returns it (see `docs/p3-record-replay.md`, Steps). step: func() -> u32; + // The last step. + current-step: func() -> u32; // Waits until the executor's run in `step`, or returns at once if the count is already there: - // replay's `start` waits for the step before an export call's. + // `start` waits for the step before an export call's, which fuzz mode chooses, and replay's + // trace has. wait-until-step: async func(step: u32); // Whether the exports component is dropping mocks of the imports component itself, e.g. those // it made for the borrowed arguments of an export call: the host's drops, not the guest's. diff --git a/assets/recorder.one_byte_page.wasm b/assets/recorder.one_byte_page.wasm index a6cfd65fe251488e05af6d221b661d73c9760697..000267f05859f64cf02b140c5b4c6c1d78ebd66c 100644 GIT binary patch delta 108103 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

F)OQe) z{2(>q%zZSsPWA6H3wIo%ud55znu)(0qNQrVDgmQ_*B^#02ku3OL5T{Nio8Ppi^Jr% z)PJs}Wla%(<1i%L;-;AbkFZ4;R#_TM!Du}~)oN%^)LFofA0ZsiZ6^4KM`2s5Tx9}leF@D}waLWviI<3}&1Y0a zc+kQrm<9DVctp2pBO~%UjH@ah2^k}bh94p@RU^c4z}H`Az0APi9sJiXQ|Sn6QtYK!j>X%h6x9M_L#Udic1qN zv!fCNw!=kyrTDLdz=UqG+Uz_uPK{3#X+R5Y@9OTv-6rla7!d$MVR0OZ$5>9<<2WWf zju91RFip{PDdE^~G=C8v8pp>BX30T5vk3`R$k?K&m|q*G#Vo;pGmhz1CmcA%h(ra2 zEMC+mrxcK5LP~1iA>Ih`nh7c}LY7MSgQE+zymf*SIdw8oFRx4acP2m`b^c(2LaHUe zSKgxhB$1amYQ|v`D(Kbnl*)x(6XE}55i%E(WIW4*9g5iQZ zgn04x?J?OG(TOnseG(KUCpl{M>mnn381o5`GaV8#ZMTw_ zuF{CZo*2uWTLsR{N`AUd6JL7*BA1_UJweg+cQq9-X9WFN#{k?6@v%SzZn?uk#rceB zH(HW4$IbDY+L|UG%at{{dX~t!mbq~sADe=i-KX*UCqVLvw_k^==bb6w+NXzj%Ucvw zQR$;8DlYI@O$eK}cP$Yjb_#+mIpWM$efDHR&o7MojC|RQsDuHdsdPha*D1{Ze3A;1 zOLFW$*!k;_V0KxWp%k)OrHIuj#jIWlVuOdUzr)zx5$x_L zHg^dIuK2uG2Ww_>?*^D+sSu0PV>*rVg45)*EtG95(dosfVIN-S^O|HXCO&e-;KA0v zTPw-RlZv*sFgLSae|z7ySXYNu>dDL%$_MP4FQ3I~E9V!#3w3c(7AUELKrQ{`6)J41 z%qo&+BWP7w@FW4M&Z;J|_?kOX+3!v!kA4k7uDX(*=0EOc_t~K z8c-jRIC+I!TrwEPs&qy%WX16biiC5rVTtgcj4rzQSFccgl}B<2uU(LKN%Vb2>QO|h zq@O_=^Y|YpA#x{Pr-&emJoq|jv40u=(GEPRVGI!!f*JzV|Pg8ukyk&|)4Ugh_>U>O3OOD%fu~@+J{17~5Se|N0 zb37~mcnU(0Z9ayV9CrGdA-G*2?wFt1IR(rRSjH%L9&_;v;^vl|E*$sJj`pr@ud$_T zaG*EVzVjP>K3zVupOKz-`v&}7OCB{WPo<0{; zcQm#j`A?`3&9`XSpUqC~l3yAof3yhx=$Pe)s&keDLTHLO%a*z3%0}*aBpc6sqKuc1 zpQ60wJ~Sf2W6ptY&zd!(oerGEuZX+MO!@m%Qvt@M@ss4 zKZ<+=B=0Pok$Q_vskV4Vhz&Y31=LUuwj=DmDS0(KM|6z1XCyb&+lY!Kx}@L>b;(yt zttnPoMuK~mJ9l>W5A5uY!SQ6EbCE8C4=OMYnC>c_TLw_pX_i&#l4DlO-q!LTyiHr^ Me>nC0soRwJ7oCI^z5oCK delta 78789 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 diff --git a/assets/recorder.wasm b/assets/recorder.wasm index 1312fcced783c00f14ee2c39706c4533fab31fc8..0bd009d71fd7965978a3852aee94f502a033a820 100644 GIT binary patch delta 107644 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�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%0A-oSkpk?+tkV@td*~S4)klmpq2UfIoimoc z)p)sR9pD=elBrgg$&7Q;<6Tfv!9EwRwR*gE5Jsk&Hf!LueK5#N>YX?Qt*Caj)n?rx zaKgIjorkDYUHOWt2zOR^=(2LX4erTpS|%f}qqwRfNhruD8vZ}PRE-cn0mplt^)u77 zH97g`Kc%V+Yf&8bsu{mmrkGEf)*oQtyzLYL^HCO6S1})(ms5TCb&2>THNxsOAO0@x znQ0fr-$;FwtJ6iD9L*4;$&@XZvdfe#bciHhbCDV{>>c(Wu1dY#sj#G2eFK65*iuBq zutr5wSQCN^*fNn4F8J&wacLHp7F_0*Dxlzi6Zb0djd%eb-LTo=cq^uHw@MgNF~S;P zM#6Umce}VJU{AnMcrw0E$5_FvZ*XD|zQG9CGMT1mx)iSL_?~{4j~<5012bUJQxxuig!5CU3zkLXcu_(gevCA`n8s4!~b z%PoMw6v}=IqvX&L@^~9%S(CgjLk*7|gYJCc2-P9J`N|PI)!^5TfL+-{A5F2xvN2aE z8vzAS9K0=ylK?Xtbyf4|F^ZKbE+c@)n{LQwi282dnvnAo6N~V#vQ)(ivO8@dT@*xl zY>eWj+puAKVqVMn{bNvFJbcR-2KDkiW3&+7$fw7s7T(C8;Z_-#ZF`ei48Zy!HYajV z60&nI|K1A}b7S#JaJFp=f8Yf$pAg@QD$w4aygzCHI z_<8>n1y#SAJ?sdmel7cfBZQfpor1yC&woEfQQZDEMPd!)dsysQeBqrr=TJ=GX9>`=6xbOCa+U!%%Ym8|z|05`GYY(n0WB*5 z3dQfUIoV1kv8@ESS0lELS9)|8*qz8MkdIbk)5({}r`7s>7ST$iM*6!FIAKYRXq9wB4kg;n19&4p$H<@D+sR9ah|*GalIY76tqq_P4PcEbVrXTY$hP0 zf#j6V>$Eu6Vf=M+m)j8qQ?a5ithecGr6651;1k~O5xapVHOdlOf!bd(3t}Z{ zJR+wt=PgfiR=&j^IcDw5m~{=)#vAk(4LtkuB$ctk@i(dP*5K@7H>Z0$64HH-g=WkC zFR!HVyIu+RyhWkl(!7a@Hri2kN5YFg`2l_nj?&w1hNG~>gLWGa7 zKOhKak%IHr&Thn!q!KrvETD>3w20&g46!GJed&RYu6kkQQS-A}Xk$-T>hZqL9}2pk zEpVEM5PAMM#iDwCLnA+HYvz+jaelSvHc z^8`feAdj4Ya2yi1h#+|m{^)Tke7MLzn0`zc&>rhbbf)3Y-QCw)JZlyx`7ePB{R6O4 z!}Fk_md%4hxdn%c`EZEHC92VH%{*q!yz;u4vjvg@Wv}@vMEnT9=Ooq43nYo^9g@+RLS`vdojIO{g$OPN@gwSr zXWZAbEzfs|65hb|;stiL?^}h~;(o&l9t*gIFLOfpGA9K6D0HXbFFltJ8kT#qFQi+v z@D)Q_tdyrl5`58l3Qzz2ES0F4O5|@qKT0G6EX@g8l1qqw7^GcKsOjmxNfzjN-b>h3HqZ<+Uh?|VeMmb;yM z?%D5B_dikg-PSI*|32o3D=z3_9d^=j!m=#!vZ(eq#eUx{nx=o&j0k>Dw`S1e>1oS9 zzF?C5=UDgE9~As5(sW70k0c~YB?QVA6((Yv2@BvPPyzmxIyPPMOZ|gO2Koz1cIsW4h(sb*B9RaU1-P-S z0{Ra>^iOv4|5CDRy1SEKT3RM5Y`Y>~h!Q(LKfjnEXH5d7FKEABH2GJyq1<$E1tGI{t8F5x(%tE| zf_{3?&v|$0yyvaa*Od?JE9Xd2jvD&uOFv8R*J(>l-i^=e=N&0=2>lGDpJDVff_^ry zTgxykd_rGa)~!wRi!qeCz@%<8*R5L~;Y9tbOT1vx9^;h9`S;HIbz1Awrq&jndNy}; zv(j(R<)?)rp8ta^%scL=<9>7E@1!-rUr{k>R%Grqr!f(JFOFw`jg?C6oTD z(v(c4aH){MbWJJnFF7 zvww4h^$%Haz7&=x;)_JDBYRd?-6j7aj(9}ub@)E}PMLaa^Q_s&9siq|XGV@*5m_aU zIcoBLYsKDkWvh5pyeS?RTg3h1NwHr1RXibH5$}kBf;omc6-D+gH( z-gdEjku>RbveuP#vd&3AO3mYKj0!X~JL~Bg%^}rI+2g4>%4~bWSwTthHsHCkUd9?_ z#I@-T&?NL-MWZZ>bG>Z}c#&pTI8So3w(CzWA8>4|)m)3`7ONzveLbVYId48*TBrtf z+YOC&OxFdZe7K?&`0@s5$Zc;k7XX;=>5>=dlBVr@^vg&6H_Q8q%l*7=17o@~@pJuN zTPKJW{@iZe@q60VM*ME-HUPihcdN$l)b8}_epDIWAKyK5e*wK$Q%dCYVqKqWc?mD# ztnlkA>cnz?--@G9?w*RC_uSf*!}QX+I$ ztyBDesid0zP)QJ6QklZ4O5KQB4(xHntGg{0ZEDL#@*r(z-Zhp z86hkDW2$#3uOP09dlk*jcf>AnHx88hH&)jR*T33z2Qox%e2`V0ET*)IWC>l|WD&B` z``)9hZge-;>gj*vekGpsD|#K_PfXp|rhgaJ3@Yo0(|xa|Yua#koVa_Y8xOd9h8qvK zdp3aFW;Nq?EB|f*&NPDqhB?gOBFIuFk0cmpI-0>5!8$EuaU{>sERMS)5BL!tkJrnI zn#XZBMe{iBrZJCm)g3T7BlJnXrgpDk0Q#2s$uc(vqUf5m?Is}09wF!8Qr2isG=Y#AF_ugg||6z9-ybXC;wLstBYTcVli5 z4Rwr0+(qxXnnKS$*;G^D7J7-<%}IwV^e^f=qf1y{lNq^-d( zG|lzuU+EuLpI?-zt_ao5t^b*Y-8Ka$_y3s%E6bwP55S(?C%=5a@zVnNN!(#E&DYy= z!hy>c$C}Q5N*)K|-G6OBW%&}r<0Jtt<9pk=!!I0I4N2W^V81k@DNqcpkCUEE*={#z zMT~iu5l}sGNCBfkTsuA&sx^=ft=6oRrKN)=vZR2NKrdtyq_5~)?TSF{igc)VEvPqNq#2Ns= z5|SbK%_wV>hU&P{VkFQZClsMs*MYEfPChYZkGARoQ>@wlesE>FoNoautTg9eM_H(X z>fU9PHIyq6V@6v$>bsGnt>HR#)F`VFcg3TuVYoy_Sq->E$+WfHyiryiF8L#fjocUs zb;~Uv5pTJL(B`z>8H-7>$?Zqg%@ zD)Ed4Vx&5ckhF74yw=D47aRIXT(&oq)1}*v4Ro2f<9>ZYZ6Vx*C)`4#eWK%w@&XD$ z&ek2vd($vQprS(E6T<`P9-tc|?ti}H;Thc%hh7qH6uRe_A*6er9a1-_BQYr4Jn{qt zVPs$;WMVW~Gjtfic+k+^^?{lxBsEh=B>>c z;;`XA(Nhuh26)*xtVjDQz8%&W>f~Hx`Sp!egw6?#b%zG%6ogJ-UJn#e7~9L4Mp#in z3=owjh=OYq`P8WpcrIy_ZKfk14*#!)BJXEF@!E+0YAD#)_z7G*)7Y2L{!C+qUo^6q zZhMZbJ9bg1>;;&U&>a73xh&3Tn`O{8kixXH$XkC2_PK)8{>P-ww@U@0uby@wv) zcOm~mT^SV>8DuRARFuh6C+8!7olGwf#$^#BB3a)s@f<=Y{m#0FsqwRlzlEUSLWT}QAJ<8fcLv`9{G0{JLR8@2*z);~|IjTpx z58V-HZq%qIhI;Q&)^1!z>^T~C9Q8>|9Bqx)HSRIW+8uW#qpWebo)Ni@W$n%YluZ29?e?n#nD6hcbJbDM$6c2g)wBzeh0B=)6NKK3Cm~+ zIld^`Myw&&Lt+i8pH#6PW^_KI-AIFxcEiYkM*KI%WNEh;>mbzb@4GW;w|jT4E8(Uy zjriMkuBN^e?=ox;qmjV4)e>PaH?da{7}`xx&?JKg_)&KS_si=>}7jy!B+FBrFrZy@)dTHPm7{dzIW-$RuUh> zVfS3Ol52V+tZ99AO~Q-sWdvAmlVMd)>#FKd)eE^*5f3L!;X8HGhx7ay`;5uyqrY;W zgX&(of#GQ&cy!MUJf`@3{qkJDe#(j6Og{s#bj|1btESWo-#=|?_a-vMEv?W<#6a+2 zSfJOl6e^h#NP-*V6i00vu?8!y-yneNubNt}W`7{6yHungAjvYv1#SuAiR$tXMC#Wo zDfp0pp@COrq_PFwRb(1q8RnHWJ1aQQ6=Y*LC|kvBkbst!5Tgyv+RTY>6aMK_C+fCi zZQD+8+xez#wSA!|*8*2JhUmZw|G0gt3NzKJPa-1KQ$JI0>!cHU!DS@AofT?_s7OM3 zZ~C2%nRi!LMMXEfnDx88D!C*Fpo3Tj^d6N8vsd}Gzju&(`0pu>eBtl2BhO`F zLOOEY{CkR0$_HBfIj2?^ zey(XR>fGaBdTK-Av$ru7InGV~##8T>Yg_yUr}fT$yXv(5*^lp@R$utFu0lF@`xU3x zmA=E&1aP-%vRdO$J-sZQ=iC)Un~0u3G$~OWEhs1;vO(tUVnR`j$p=;yx?N;>R#M>q zC=CtUS`{g{4kzOtcEcPH05tAwJmKY$`sED$aypa+$PJX1j8ai#I3=xf%+!}@$haB2 zujwF$Oxz|^B;9494+;cX9XPBf8Zr!W<^?%3WSGbUF&mZ&(Rw%adW7sANklTKA;#|U z-#opWSnB`%^kgR(GK^&~B>#Ib1TGEw&4|s$5RFcU7~=Ud%9+6sWeo$A@r{L|FbGd> zv;4Et-Tc2^c<}B~WI_ZK8sU!_ilKgMx|s-31h*xR$$~O^;zSy53=xcINly_^CS(4t zE!CBiib*WlXqSRS1xVfw>j_%GKe?q}8kXur-I((LF$+c;ZN!l01PbT08_RnLo}_?B z-ljg3D&UA3`fLaw!$_I{#t{a`>B2DC>n*F%?=iP>sDYuCI?Pi)FA^lWt4$szDjAKY zAbR1UXNxcCTKKJIx(N7o)q?V>yrRNHyggV1n7?|i>1u&zPk@*;==DCanga!JBP+1` zscASJ8W4>VJ5ZOo9ZD9G=th@flpl&Al}FAGDzB4?WE^vP89dr|ignUSam~M%DZO8%QcSr>yXnKQNs-DCJ1VjS= z7f})XpKtrg9jbewuBpXQA?=6-QI01IZgn6siv1C1_DdreOXxv$Mr6cEWF*>(jEsP1 zkx`5QqArDAT&H`%kf#8JVR5+doQtB;5r`O6ThzANA8It$#~dw+iW8W3Z3pLe11D-I z_MldQW&#+$7m9%6=Vx;~bu!ZuwX7v#X$~er$FGf;D%G_27%&}o&LNx-SQ8P1!DSpg z1}s@2w>*$}rHv%?2;1Ovm^q=8Xj5-7$)J$Jq+8^lv!GWj=#E;ogXo@)jYmBWL~bCO z;+=@?VzOv3OQEGFA~rCmDP~TR6{`S9vcg=Vd4p2S8{h)5KywO-Q5MZ9AaLVC5@5#7 zYP3lh@Z6i7ggHjHPNRK_zN=_NSe$OLIDkTcoylmmX@KaK_TB?O-A39c?zS@&Sv(gN z6=jL%1mfXZJnOM%X`-nhOFTn}7Pb-3tgCV*4p=Q*dK4P6hN8n@&kq6LY1LH>W6n@x z$OR^a>6OJgla!WOft1o>9h{y;2AGiVLen9nJ&9;BV*=#+XP?{C%=e;4iDp7`VXJE; zi|IJ$DlL8`L=*kd(6C8)dJ-!;K||RlWsKEGl*~yF>9#48DXHyC(vE|ySZH6y*czlz zFh;H3N|!WRCHgkjXo33ZrPXLbK2WeU(4yMjifB38#sqDnPXuvsR-dv|%EVMGR4IGH z!a&9}3z!Iu4Rkl(J3S*4FVHd(&L0!JAP*+DC!`$R0z|tQS7hXpKFRce?=rpOp6Xtq zEw(o;v~RHgvkXpcR??I#flXqQBs7C9=+*wPE<^5XBP!XXJUz>#N@x7t2}= zOkfxC{=uiGO-50kf(+QVo!pE4h=ZKSvg|SrvO^AX8($bSmd4u-O*=TfA(xUcM@Qiq zx3}!HgN!zfed}ZiStdmANj95uPIizfXGJ;;)PTN>*bE6;i_L6HIAqd*HH|TxWaViv z2~b&RHhZUPOk(!2Gfbu}Mr9`=?-!x+--XGei2O~gK~py)h^aXnl`MTaLL~$R!cK(B z5R=6I+d@0};2h;qw)8A+q2@r(a93z#DEtB}P6OlzH1b z_J=VsnANes>&4r)UT;x`yxT|{SSg{|hj|Iu_b};LvPtR*>_Wp9mzj=es}5?uz@?0V zRaX$OPn-G+w1*7Wn0#WGb7a+_A+qD(A(K=qY-HmOt3WEi;)RX&3|#PJEH{{Q#QNHX zn<+7vb<$U=s3X7H*lZL)yH%SW9lO-=cs$EyD|BPVW~1;4;SS;ig9FPwOPl~N2F|^0g!Zk!6hs7R^Lx5ZHDC3nsE-1^J$} zCj@oY7{pEdAZ-yn5u!k3B&j_@ z!g+&0@Ji1mJSh-RXPYU`GY=iNy*zB2aD>9pW9+Vx#HN5-%#CA(3Y1bDj~rJd72l={ ze= z9gt1*2Nlfln?kt=P8PvW_isBpDf0Z=m-b0(1Bx^{W|+zFL6<3j3jZyJ$viT6aK}5c z9DRpfT3B;R@2o~?(ZpPCH?U&I_}^{Jvk-dYwNRV>9!ul?f_ld>sfl3-5?zu_Z-;EHnSBNP_YM2ov71@eGbNI+3Pi%@s>a3njQGJ+`ssm^63;xV^HSc0!LMIGJ0aMYThF@!dgQpM&~hRDO#}QH0$Y0 z*ZNOcj1+{m!Zl9nfdV;H-g$%iv_O{Sy~eA~)T-;n16|L)v9$%(60J#lK~G?vuUeLi zq}hooAT$aQOjxdNUoLi$54Wt@wp<))r*l@`Ls~qltLchr9@EwQO)qUTT>PJ$BmK{5 zrqfo^|K$F*1w?JQNhH;j^F^}Cj0D6v!m!OL&=ux$Jp4J&sT~|SP~CIB=p|cQ)CrF4 zA)ZqU9ZbutRHdb|d;CTg+%^{2FIO$^i;?R23q)`6w)*S>G0M@25lk2%P)#|;Tqu@x z%b9j1tJ)=7SbcV(I260N_gy4*z-8_tfbrcK>e@x(BsAH5vG`qV(XG5-WSv^FSPU%> zo|pr0TnA}?Tiw1`R1Nt;(~o8jj(?ysuXivFZIbmgD@;&oeb7Z>usBJ%>qIyI@sIW_ zxK%Tm*PxEOMvSA*&%TvAU;2UQm;PGF81_5JCiGqmY_(*72x<0!CQ_T@K?xS+mxTZ_bm2{)m6f7B8yebHs>nySWWGXuJ7) z|L8AXTyy$4Vh>UJBGr#-%VOXeAOHP;4&L6_(OWh3JTU;RoOYh56)*aYKa34T z2KP=&yZF;WM%#=8pPFFPNbnFu^nm1yM5m)*>jA)c0Ef<7ga*PZYHLo~=>WeGZ%T72 z@-vZZ!{CR}#mL3t;C9G`Ul1bqFl?R8A}Mg8-kz*E36m_4qc;#xDVx#&qTyW(68sQ~ zykh?hMPBigz=cRtPp}K^cf`Lb!B!nR*ceJ1bZw(JcvC^dpYeIWbaYY?tyUqT%_q*X zYp^J~#q!Rk-)5eY!r=nEO`^g>W58NyYi(dT4peQvx)qs$y9yfqFEq(LNHx%c8$*ek zb#Bado=lVuw1RhN`Y!^KPLgw~|F|b9tT@0_84WG0L}D^bW)YKt@`=e3Qaf=cq>4OR zN(b<EFq7n!?sBP{MhJ)!?rwYw5o-7ryee&f`J@hsN@7Ux_gno>h*FEsT(aWu%aR_ z9}Aaa7*LE?9mNIeb}!-$M789s)bhh1%cT_8q&O)Kz&qxV=QI-7mUlNv3thN6h1Ee= zN=sRaDPRw(r~t}=TD-3C(zq?as7CZKq#OuG9+2I!A_aLZw}5^eQzzjDs~NCMh%N=_ z4Hh+^-ogZ-2l=Z_YiMW?mYe`soJqk@CXgK&5-F6KfQ5Dl_2ZJJcV)UCWt0<-4%Gob zOq&I?P%o=C?6Jx&!jEl=@Vi=FD=^28z#JB{gtf+KYCSS29RkTaxH6FNW1DLEU8Y)o zY*TGM&B12@i|t|A!17~ut!WGtzsyw2k8P^WqZy(QHM(u%MRqzE`W74h8IKCHp9$Bart3_&PC^@5p;VA`VwXnTk!o5dGu zk3bMWOdzlDMmDD$fAZHo(!m6r5rV~`cq#FQWyDJfWC}?bUb82>*B~ULR26(-((>@; zaqlIPB`n>Dwh@!GVj_kEHr$04R#;j)+5iBP z7ty%cKI;2S?i(wkm6w2-zCYX#A35G)pPfw_2HEH2P^I0hj``9@+>ARZgA9u1ee zWEZ-)$*#y19hHW0QQ&rMB(8D0G$LeH;Fi*PN(Ho3tPXOrh_H>w8XwKmOi0hus|$J2 z2#)LFcOWvt1Y|wENnuPTv2ISd!7i2;a_t?V6F|k@W4S3%8LTeD5x2-?<1UwJJqq{} z(AZ=Xv<6e*KyrH-n?jc%lxQoVi>}a9Ol!T= zwh)c{M}JqwYym?n=5N|QR(y6znHWCUjjmu`fl`2~Bq7P`o{6VZ`4nej8C~EllrWyK zB>aGTlT!!@L~xRk17?YPCpSZXWBYz{lJ-kNK%2!a-UHe;C?ZiVJ&FR$NmW|Bl7e^T zEVt-VA!-ne)lK9L78lttmJ_p+w00BbfNUZTbmIW9AemrO<`)3)P!0ezVGzv%0M7uR zDkYUIME710F^K-siBSlO(ab>}I>lDa5<8ivUJ_ylWlNDfb`3ERfugrEMzWT~&({>7 zJrl@0z-p!mIJ?`d%TO?O5jiLEYwZ~xHm}f`H=l|o;DW%(`;N1jhZfkgVanG?@rOJJ z$o=Tl15;*YBNjkrmN~+Q2_OD5_IjbI_a2(lONVmESwsI?ZXuS#%>ur09`pb8;R%Rz zKKL+2I#)eX;m2C5_-=IXt-6bFAiX8bsUGHrG@>ZL?|=HxF+ZpBi#w{k{M$_BD}&1K z^~Ksr2;TUIKDz7R^KWVeq+S#oXfbG~cwpVejaA6xuWeY^XygUif6@^@c13HOU}e}(@felPRI6Z`RRnSbmPyNXNkT;@NByUSO9 z|HN^EI#If@mw)b)UE)g&l+G6|{)#7E@qoYn$^JUSZgjcw*`3o_D8V^ra}CyLnNWze zrMen&6%i3RqoXX>W#dC5KBtP6VLGAo8zKmd4OTb#Up`ee`n;Q4Y1o8wGu3TY^(4U? zBw%p>XHGJiuK`0nag#s!X$swiU4B+KlxMnJ@Br0_QypUd#wSy`T_2j&btltx+BFq+ z{bpTVR1?g%E&loqwZ9Nt7_b4jFWlt!d#1O1lA;vAsW_;a;`a}oMe_!hqRWPLV{KhUT9jTd_P=WKH17YGXO zE>a-*hc@-0gzx_}8W;8H6jA2y{X%_4RM-ZpJhBxsP=&-nwh~0e1<#Mr^^d1Z(VtG% zPd~45Z*=Cs9w6?|!IfN|Ggkiey`QbUujaYr~pN|oRp}@G;-|*sJiaCxco4giS z{|IqC_|gIUeW`0gz!Yx1v)y2mq+mrL1-}ZjIREG<{hI(7GP#`ZTl}LpGcbSLJZGHI zL9miwbsBro_7-Xm9l*niJ}e0|#!9U*E+Bnp;Zy@aiZkn`zWi(1dW(O{%lnJFefdfQ zeuwh!k+1AJcCD@};e5+MI?~k6-|05VCNDjQ47En*jpyd|-+kqP`2DxElG2KJYi{u; zyn3KG$G`H`y5cL&;Un_|%ryj=_3N>c{Ox>E-kC13?!RrgD` z_z;Z%qE68JkcpPJ#NYdkiXMEvp|*{PQ^-7OKoH#W&hcB`sDLWC>nd=N_kjfe~ zR7CzJrYm^{+-yws-+H45ihS}`UGdHHvWxgb-W=BRE{!O%*j>AdT$&6p=lDf$QEN-z zbc-JjI?Ap2PrX@X>b!k!PMy_Tf`q%~=Oi4zr5{f2qY9V$w{58_-ke+EyKR!+$xWXA zR!-^i4LQjznmqhXZgnQKsQSF^rk~Z+Qe@1pEW_;8fabJemX-3ifWq3Pe5O;n zw6ATx9@|8ll)4tnTcndZD0GcZiKCGJ;uOp@{4-6ybM`$%eD1%u@6zsnB7F%qrhQwW zB_^nd^RR!#enaH#bN%P`>ybH)OnP;CrMv%+{i-sD0-#ueNRZ2#MHs7x?%yE7%GcBC zCW=WBwW9JX5AEhJ`qf3WN4Viw^+*_Tzm~viuXno(H9+NtU z5>F0#P0ikp)od&Lj}Gk(b}E{&qAdYVqXN@W+0!!!>Q82ri--N_VVB|ls>8~wbr1B0 z5)bE!uBRSs0b`&C{>H;7^Is1eR{klCIpPf%<0z1gIB3+_<_|u+XWQl`QgeGU?A`p| z9X?-N@BeW4fsxG-5mjf86RExrvo45264UMu@}Uql!a^!k3}2K2fhlYwRnLzTLtq@J zyz!zhA}$T%g*z~-C=5;lhc@;{5%QxmJxv99rh@c%(L*dymygHl64diN>e&^`S(+*O zOwEz69-DZ}Nl|l-CUXSsN=~2_Bo%7ub~&ZBu- zY$8H$=wvMP!h7w5N9Nii40uSVCK1g-GgQ5sH_H=~sVx96VKQK}Thz4^M5;``G6fJJ z|2%@TJg$1Z%0Kyt-&AN~fY3n%kq{kn^FNNbrCnkudRmX{-!72_7YRLiR870YP$=zu z6w%CKM|B&*bb`1QMdX;&hv9;`rGU^gFon|RhNG?-G@rPWV$HUPNFqXUQ1me4bw6yl zS`?>*+<-U*ETr3R%G>0?!B1M)Ej*@1emvJ-bHrL`7_iLld+gD69wsT}4k+Ya0x@PQk_0nEqWUVG7vPmd%WC1stjPV9k z+3evkP)mKhmq?~N%1L|F;H{dxuhO}ZATF5P_{Cc|mPE_A5Opzq0X9luzT@}IG+YC? z?scjGLYxiI2&j-oRL`>wlNdEr310|xpngREiTlt19>9H=?L+1=3N#`XKAVDyQZ1P% zTy^YJQL=v)_{6s%zjD*ySHcjuk(vbm=vFdqP_UBXl4#*%Osi9H8Lg`AlSO5pteQJS zA$Lk0GD&oo56o4I;TMUkRnxwLgDG&MDyeA_C6c(7KmhMzV%K<9`P#FNs$Hju+U)V_ zup2yHXYL_-rx{O~9?--@6fsADP0XKS34oti%9`cbS_~jqlQm3~m;_)z3C19lZHOOy z{K2Zf zfyCvAE4V&4ui3S}E2N?0;)J0r;rbs$ElmnD0&&1p0kgCljX$my@AHc>`1)RA$jG0f zQO2MqJ*+*})K3to;(bL`cSC_~#`F9ihlcb}$3)ze1x=?!j1h@CuM)4fBcy*q!<##7 z_@e(>w!LbqsO<8k3B6{G!BbO3RlOMl(g{3HCer<>&Q2jBpz5cF?4Qwrvvg3yh){;; z$2zcGcOX0ae=p6w)Zx&t|M8*knArdLD={Ejcy&(fkQShG7GBabOTXwHNh+4mycmTf zakQtPEum9ULmn^Oa%d}}GbyXiva}&+4YF{gH~eADFA(jeS(FT`2@`?p(|vR8i=Tt1_L5!ZO7=V>+4I^z#CXXA|E|Hgvz>Hme z+;OfjR%+C%#VNZK2EY%(0BX+XqTIjd_&F7yaO;>^jC$B&UW*H?=>q2kzv(w+g7Y2i z-~XGL0Hk(K00_En>SJezOy*g0`WrwpiA=iG%Uk9QS6@X$k4)SGCfCnA%rH+!LH+8j z6X964|3uJRTj>DApZr8nOji?sPwhHZbXP~^i(&GS7Ij6w7+rdgj-U#!RYy?Q@HXRJ zDLQ;7VTa6eC&fg9JFKrG+cG@-L^`W=ri|tNGA(LXC)A5>Y1q^lP?}uP+n;fV?SI;y z#|6F-MjY!cW`n|Tv~K2)f){8Rc5?-iaNZ+qU936Lr(nRGp;P#gT5pL)t6tv~G~y(G ze&;meAY=)EXsb^h)?5<3g4wa?#?>dgai?m@k%Ea~lj#h?8)PNnh*FykL7O=FklREm z7Hb4;R)%dR!#3-5o5VZR`3OLq!AAWvo%=P z5x*WWgAPWMht7;9mNQY~@ZH>SG|OZY{pF4$)(*8BN0&db z(@AHIV?}sEUp3rBHtKm8(Tzb?B^L1n!^lnI16LvGpe>A_X$#|L+QRsuEy9Egs3UY< z7NGfwlpF%FdZ0?Ax*rCp%y-qR_ftqa| zL{j8c!+BUew+wLLK0_L}7l=Y7MvAfq@O3t;|0Hqvt9o}!XLEQ*lz|Y2p^VCqGR(Mv zGU{ZL{=;CM+FUFx0V&SV;g`O$bfR-EVUAXrqh1B5O2AnmS)qx#D_;(87Z_2>;?cIMNpM(szR7PSwIM+@Th@nVv6>S?p_3uNm37^3!&F>_vuJWPs`Qx zkK_uGqrno{srP>Vv+wh8uESWE_3fRG z1sS=4prHeQ?ZN+cQkBN`E+ z$VK^(m9sVCFHnfQH=TBMpN?pfQH=Sdvofa8dmRca3IZFBC5G)J6H!jq2~|u=B;JSx zjPAvjMPd&zBF(%5inFws!^KFANT7Tp?UZj&JL_IPwT4d;!{?7WA5ShbvO*&Yoac7oFp|i~JdVyK`ni6tMLLKJx(aXeEbQi*H_ zYWs(bLFhZQS1*T=L^t%vy{o`}$24gZBpVVm{em_${em`hkqW8hfCTa7DlJ+y@B!@Y zY_b4c<{WsWT8NfuU4s5VuV85^=wd`OQv#b|EHFuKLZ%YYpNI z#CmBw^s!B@rXv{Nq4sHKavS*w*v?}4n4_w&!#c(QaYh?$al!lzCagX9A9Ua-T42`_nxTympC5JPNj52M`^Tv$`Hd?pv#sBtLufq+sA;5J4pw%0V;BXueS zHH~GCnOjE(ey~H4ZPK<6gyEzdJ}m}D?SjCl&6q2l^)bg>=_C_Lc5`SB`Cz0^*~Mo3 zl4oP=+J%K_+lK8R0^8wsd{@pkQ=}$$*B)do#Kaz{ zPTZc4TN*Nx9mL~YM17(oE)7#T?VARrqj z`g2}JHs#G6tVlL{1AGKb+2aoQKru)RZj9avjR_l=pi>9&i?ym>8Oei|r@`Wq@HuSW zmQ|0>t>zIx^wre>y^-d$V@89~2@I0UKpjA%{GE5~iwxlhQKC=$kxeio-=0D?5#P5MZUclw{R8d?Na(OC*9V`>o5iraSMYA=e{$w?1>9lAKvCneFuV=q3=Kt z(?W=uAU1Gs>CDS4iqP~B$gwx)v?@Et^b+$w{TJ|zX|!2|)C1wmx!0q>a} z3@wNB0BR?CK#T+*_Z}gc9*~FW0oP9G0mww8{;@OjW>8VSds-%%7TFwNz(35&_5M*=-W(; z1Q2}=67ZgpfcH5}fyU8dQgSGbqIM!f=QK^voJSZ!1&kq7z!<_c8$bx4! zB>t>6)QAIn1*;l)4Vqci7$Fga7dU*ws}g8IH+NU?F(mrCa{PIRMwvQO%hFgQ~WfjC9n$eioW1}waBc>f#iw*%;mox>>PSBHy z9bop+nEm7nz~)Gx!-7NvXC^I#cCme$E(nFwwiA8Ex~+cGH~h{OdL)QxSE!HcL=|?3 z-PTvs?9vruDC;c@GH^33y~76rQSqDH@{ZnSV74(*gO+Tj-E^b-fl?Ki5c z`(a1e8T~|0dDczzu!Eb~wbV(!Kk4`1*Y9r1trtb!Pj|ks^Uhz1sT3Y};FX*_P@fl3 z7u1WS`7{^n1{h^vL6UQp_X8i)2Ykb9g!8X%?<2%Fom^4!g`grd1G^w6@`4pFomQ+028pvu7q+Q%wTAAopwgcW z-34tbx=UB|&M&Tr;}==@xm6ROB4Zc65Q^x2EJzwSH1-uf$w92Av5#@M$kd>yn4$T`~F|QKlkoiuUzvNYhN7qLzKpI z$&AszesJ#3=3g{!#}1Lkt zrjGgFgr}LmkfL(&^w$qIG4a51CXMjfQ{to(!KlB4zBd2Qq_1Bj@wB`%oCxv9y!iHK ztUd`>fdqW(J-iC!oMz8=w_fr&ksUBRh}5tJ1S2?=RE{SK+q0{h;ECUq{-nk9jwjqo zJ-r=%C_L+MW@BL%sa?jRwx44*m8orGMWXX6*>(~~`*U8YRx_^^-G=K$gWB3BlON0U z$X;Z7HFJ;!jGOS(P!ZpvnqCv1!8aTFx)@l-gsa2g-ecxV&FZAr#U^AJ|At8JzMKtE z$T^r$5$$4=b>+fjF>7){6Nz$$+4zpKX}4e9{)Tu2b)NX97=X)_Z;FR-wB)~R5y?^p zfqexGPs)bPbKw@zORQLP>lRTi^7Ubm=c=dP68MU}+VSnqpg!Sk@qSUjh~%K&ppN;A zSdTId?}&a~!;}CQ@rTzmza!!T8UFR%&MLk8U9l-xpZMv6-7uv_{_S#g<6lM9#2m;? z*n@VO=>>-%jL^0;-tyqTvC};HZ(=oo+Wt2&AVN*3ZFh@C za{?#m8m$kyS&T0FGi{$HUl{;Bq<+5!o6rz0xLNEJh7u6o9uxWhNy$b6rC*Ai>^-UCX;{~X!|IG$V^I7ys_n(!gKk=<@ zm?qrRZ8Yx2VHaZ>3VY0Kw&ABNUo}|fJ6cDb-8fK$43nrV+B6Sv-smX4e1wkyX#?Iu4w9OOI zSt6M%bUsr~OHrLAn#sbZ9`z6~=eZ>Wah_W`A>~xj31X0jSYYHL{FZP_)X~#L-!vcA zhgEoV;5{9IhAEJEhj`A%<|6bj8;g3yVkb!C#F7jWY2LRz64TmLE#Z|P+_#|8EG(L; zLM#SZvXE%9gh&jsWFgUH36aRphXFxZSTuP;ECT4ZSR_G4^0qCxm1%Nwl8(VMyi}La zyx|a0n+8@rta0*!gD7v;Qtv}zaqu`KZZ*oI00rStYF3g+v6$>n>|>*lTz(SYfdada zvig|p7!7PAzyY(^N>W0kA#rdE?KaZI`FMRm-;4w0(oL>I*1f~B3GY&+q$dkeig^j* z3eA;bT@6u=3np>#z`B$U(JPuzg@fN5xVzjt@L6dU4umTOf#Ifv+*+_6t>u(h`GcS8 zW+XYz1{O15!n6xc1*1qh1(KpVrppP_2km))wMzhfVpcLA?{Ne0f-YxQoUbe9R#ACS zHyH>TK`4!53`xS6Uh3~HVeth-v%`H&ps)PNMtmEP4*8-k1LUAg&}-m4A78{wR3Qt7 zg>Jw{=qjy0$#k`2h}eB#h|+ziJG7>m1sD$N3&9KeItyR()zD?v45%>lB%-i`t*XO* z8*k5KB9yi7%PttgRDKmYkdF@F@UPRG(JS1ef9Q2%_<0{%ZQhMLMn1=wI# zSh`5hM>>DAnzw$}pWN&%wK4amGZE(Uk<-SEH86(@8pn!mgxdW6zwIe(f8uXme+2 z$83Y%Ouk@EQars*2pk6udQGGRT_Pzt%d0q^H}Qij^inOF(6pNft&CcUg^`denRz?u zghn(*7YqC}M``jW3oW!GW#;R2oUIS=W!NOgki^>xJ&Q`Vr26DCQ4!Bfuf^5!!^P0R zx58GI<|N5UWEIrm_zU?W#+|bClq+@2rO9RPD98%WG0cHef0-<^-s1&OOJJco46JwC6fn|kY7?=6ia=$0;yNi%2|<*^CtjINY4sLr)TD;3`(RUKiK<6l z!=^Es5(6CqdMLb3$i*6qb6c6@m@YlcQ&}Jj$b1IJ021;FKmp@0?aL-=i3x(|G{H{H zc2PqYBAp>1bdz8P7Dz-B-%To#Je?Wl4l{R1txO^4h~`kt6l(<2cP4O#Br_ub@F5FAyfWg4uZtA;yu>L4H1Nz_a+J2%8*Ecw*WjgxVUD zxX^GCZGonkw&;+IfcSCFROy>y;PfbAL-Rj|fE%OAnF{Ez9j-&mk4QM8)G?Zh5VWrz z@`Kq4ZMDg#2HqhC_u)f5%$ZYk;8gI1`POS%TQJ=(X)(-=s^tzbw&!_d2j!f$j>9O- zp{>i*n|FvFMK90eQZ}tqy;0?@7EPTt@Iu=L%2ax_7%t9H&YhzBxKGaFnrPF$b0ZCl zQJtD5u*ixeccf-v2or6Vqz+)$qk&1+RJ2CJq-$C;>`w8d7`;sk@!*RCrEaiCbxUqS zVVKYwB&d;hi=G7=!^cDJ787^SUVD6;5U11nCJ=`9EM*g))Ts{$vS6}ByfWu`^}^j^ zzn|$TU$RE*TzJv#gbc61nZM?>HR5d{?o$unD=Kk$`Cf5PY|(9Nk?fqc=Cb?5`670E z@VLVRVxfq&nn$(vL9t`kw{PRBF#Y6h)Cq5>yobc7x=^~4`3u?MtR&QuiWkArMHH)9 zu4X=jgIbI~Z2HsskSL$I%(P?l9fSsEtpZP>B{wk-{0Zr7WY3b@HT=5d9zvu$z#bX* z8hblGEG|sXqs%a3@sdtBx)mp>(W5X`UZF8op-r4%@8BM`M@srSow7?ifZx^$W$hCFt`ml}OSo}0H!`qY!izegwq3%vtM}s~?a{U1 zPEKgwlPh#Wb-TLO=mb!Ts#+_C6t}Xl(Weg9oTm<6EBc7@)!enBw)nE(bUDbx59X=W zYsEft%WW$1sOT$N)qqDuPjQ}_^e94y*Q(z>D(Z?qxsIR#KVkM51#Wtj17}Y>Dta|O zr<-j=;;zl0;;)%OaI-)&!v&fd$}BPr*vNr3>nv5(kBJH5IyD1dhUs@fDEvs^lJIj4 z`K$TducRsD$rKMb?F}tZA~-E`$JfeLuXQj!eiFW~13eMISLTm; zJ6?F!UDT8p#aOIchZBNcRo$Nu2jFu26XG6R9@#1y)gkM}!QyFk-+FObj}1InG6BOM zCI}WhJY!xIs;i$wSay?|_oNt#%bF(L>Rpe!0KCEYOz+^kv&q;>fNWsTznc z7~ojd$q{RH%+q2IlYo{e0Y3<3tH+)ev&FM&#|@&NxKJIm0fW3q{dR*m1TSCMAbuw{ zsJ)*Ny8!0%pFwc=Y4yc3=)tpU%Cn-j=Y=$e*hUvaP%a722!~o1dgA2f!NRIpq!vHR zwQPGrdyYV>gQz#74FFWl^V&ep&R__XhyL_g)qUB0}8s ziWrSV&o9h3nXu#WpUlseIv$wq}1|Iqx4HZS0VCx#{Ul}xm6j*9>L`jge zIjBk}5#Q8e1Xy=u=esA!r$)UdzDAh4-895Tjl*-Zry&Ns7y!ejrbQkU+mP+2Azo68 z-=OJ(j%FM-cRb_pQr3(^nTRb30O)MWA%w8ql*7x}Qx2HInD!1tS4R^JVHF)uFuceU z43k6Cft0cH67vp8Ei=IoL{4(-Xe$X@nCxBX^~@Z@OUn5hW2Lh>hKFyK)d2K|5`-l9 zb6}o)ZSK$b64=R@=vDC^cOM}jlciM;(_?DCkHkmfZ_4`^ zUnF}&9rm%PDrti_bsk=#cPl;?%e!e26vZK8WbecK0YV{cj`&23DF2Y!pdcv=Jp?l2 z&epl=)=$J7`QcpUZWCqlqq%CtHc=10nt?~0pYhvmV!1e1RevhRROq@Q+SJVVX- zR5Z3r4cmVBQ}K3ZaM3XyYQtxM;FGy(m(N9Yre!2CTyK9aPAOy1$!;U00rChze*72W zG>Pj+Utof(eU1<2szG0hh3x@+&a{mw{i4+i-d9YZQDzjrd0FR(S&{UTEblP~5S4d%?)DAk?uI zrs*#FMl>SxQ{RYMTt4|m9FH=Gd@F{AW$=+sH-h{Hx*W>f_^lWvZdmi~x8hMLURZO( z_o7A=nFE~Bf0`wG`3Es@h@Pp64~Ok5n+)Y_dz_>+Vl+jD+>(}~NLE2dZ+l!dZWq51 z7pYIS<0Q5_u4zS9K+Tp3vf$J@TA*r{_>-yabI#)Ha&lghl;!HwDk8HVv$O<&n zt{BHGB$nw?JjXzC&8s{{5>3vxWyA2Lgn@Y0S;?PkPz`RfQ@+wEz^HoPmc3yW7DVK( zAlXR~dA*pYK8eVhD#mDbsc&_u0+bpQl?}mnHL7&jJ*(k)8Zr!~IzKA+EYOenczio5 z>%`qEo`;E=ORvqwjoK+s)|;HvrCE70)i%o&d9trqqt@oh;qF;E^sUDpLuk&*_SUHG z`LfTD+%|MBX##*L>7Az`!#BjdZCV_?p^nd&wP`aFWViB3-thvGyO#MxB|@ZlP`ZCg=bg#0;g*hmG4d9iJmSRT`5!2VXJN zd64+5hW1mCfVY%M#WA1VIY(FoO-JwuBn690FlCad!)j9p$d1ma%|N7{iOD^7+iJR2 z(WYzqJ$0=j=vph;QtkS-b)EWkfgIJx#3g>bH|pL3gpVG$gFhZCUQ>C6awqxTI^`A0 zZgf4UP!8!6Vq0%@Mxe9VIg~;flh&!X z3uXU$Ew^o~yTv3%ruSe(6q}kgL~3|kCeylcm-6FVXeR%HJ8Ar#bBP1n3_iROpdIUx zY3kH;-tw-~L-}V?81LQ1h0Rbp=V&quvu2Ojdv}e@&Pzjv(0l-ep&}>-ion4Ia*Dz@ z&N@|;kZxROfo=VL6>l)KHb#9TI-17{Yx=vw6RZr4F zU?#@Wx!rlm{F)`|vm!ZKZoW$mE|wP+tk(^EI8QxQEDtO;eKOzWQG-h$XIH2xC31R@ zWV#z9y;_1$Y>w#)BV~>wt6<3f=r?RtdsKSgL8hEozE(tmk z)8nSjESLXQaj~YHQIJnHmyxcAaheo0m#eaFvZni$IyDYdJD2MFl|tC`fv_@lcsDt; zx|OLoqsvm(APW;BgRBB)iCWc7CaYiju^br4ah2>{pOrY47LU^e6V2e6QC|5vH)KIvzg#8z_vc4f;Hwugqt946LlCE;R6fv7 zTh^&T)v{9JIz7fPK}4X$}WJ&m{J{OMT6_xu#jq-ceq1snUDYPnPKH5%18i5~9o zQq|N`UJWbr85wjNBVo8A5aAxM@3jPR_9YtE zkOf|ESyrfxrmF<@XE_bnm$!R=S)$J9Eq6vQT6@cW_&$kxzqg!Rw18y@Y(<*_l^%A` zm%YYVy9HAL-q(R0p!`O8K+u;!zJ1jwdntV9uXnfid6ZH%d=}oE%&Pu8wNp}#9D1iF zXGs$}np?=y{-s$lump5u^SNj!Z}a9k(3od zy67Ms`ztGZ=+Xb9$g~=k-z_DF4_S15EAd(9fDz)uv&>~ils&d z(suvJG{J|7Yyf+iiq{}iaha;Ckpm`b(1Di(Z~HX-3xl6>yAU-vOAF$v<7^=zzjK$m zq6Wt4>uPNcbjf+@-5NPuE_gy!*UBN)eiwGY9-;dMTI-*!nrmfsY{3)6W$_uR zq*0a^Vn6LD3)_rqu2i?w%AVpQ^>nS=*ZG>gP6&06&f~`*4C*8MRNT+F7uXz!6%x`h zoOSU!)!avpb}r^|*WdEQy5+~!U43NTufEoeUqwYjZp4VvW-4f6c#*eLQp%<&9O*67 z`jxW-FLe4k-ey-Vsp=tpR;;R-pF-xJ^J*7dZiv?bu*#HiVs!|m{(O5{bjN6RlojnG+dJ-`pa2n zXC?GY=DUDenn_HIoAFuyminT<93b9O?f^Lo#>c?}U^ZN$2^T^7Y ztB8Dfc30|Y)WFGEs|F2}b#-q9a~a^@tnUt)ARll+tB7gN{j`B{*2r(DuYd%x^NVf0 z>oYKWxA8DA9bp9*Z)MgQm)OoK+v>@$clNXIj^nf9fv2?)*w^Sq>D zU=CeAL>?sPuTx(Rf!(R)OtEvDa)-(bO`ixawT5YSAFkO$Nl0)QCJ zoP%;*0D}Byj*w#@G1iWd^=9m&Sz{l~jD3Ei+@)v>>$-opCamuj`m*deI%m;{ z1f~ew)Lll(f!}LXfUi8%E0h z>8mrW3(-p4TNj2XvaC$zYgbnRJYRoSD^a=hTxcL~qjn;UfA~>E+G+dr8dQwt+*zqe zE#KC#rIdt~3I#!ie;(*k{dOl=K~~a*J85g_#+}$&`twe5VbQW2p?mzOY{LSh8yXh3 zjmj7nfsxd8w4C&-98Z~;ohGEwlH4K^M_2+7o2lnb7^U{~pm*0tuL(GUat zm7f3zB-~?K4!XHa*E3hWzpM1%tq*9D50MJ_ut^>jnA=9lsY5)u6XtW5c$uVtVd7)u zkuVeHjFpqb8S1{VaFv&+H^$1Hsji;8!Ejow4&MzX$GPgp-DJ&#ExK6~Gcl4O;@zrg ztSc=4R-MD}g`1D*=>j8!GMcUHI5`4U?lBINa;w$v5u3rK>v%a2K&bHy#Qozn5Z{iM zBeQhk=n3+Kpnnk)Ww>vGd@#R-#wnGpSGf8E)h(9VKAxwx?Iruj&3CBo zd&2^JMeVt_93t_$WpCMAZn#5TO|KWJ7x$L?!@3!;4{Y2OYWIC$|F)=2`@mycqAuA7 z2J{)h)93qOo?yAEo+2AYYFrhd-F5nwhSc`9Xz6Nun}Q6CcH6r>;8=H0yG$*aB40t% zCry=O#iBKLPeq$}e0yIxHj%N&k`eXDRD{9C?I&j-wRC?uP;J@|Ga`I{MCBD(gK$mO z{Z;Dx{pBHY(HU72(kvnxRp|k;Z{?X;d9Tj8U#zAaAorFl?^II{#Mr^Fzxx2hGdJF& zK0iQC&whW*f%4Z?0bP-N*zqhs(`|g7uLZ}pLd8XZ)?CmcnB~A@V`Ao zPRx>1lMj_O0~tYNCmZxgOs&wQ9dQZ;PV|CFN$Vx=QbApWBWU~TBV>izj*Lj@eFRd@RmUC?r0kr!j3g?_tkA~1^L}zo zz`-2-cFw%?NZEhL63Ql<=#Mx*!3*wTWpL;~ufdfR(&XI%v~BVUHT_6A5vJ(%N6J){ z_Hk)3brNglX?=%Yy==o5e)~u{vt%LFADF{;-meZiN>&&0OK%;ClJrrs{D6A`$jmkn zSjNEmeXJ8Kihr|N+5qY3L4py2k<3;WK*K&2li@L1l}$N-bP31pgQKL|`(o}%)~vdx z-*EH-i6&U6Rj96XrggWDBS>JU%gM%gBp;i8bdgy35N+QFz zN5d$>wfY!21VgjWF>=`d*W7!6M^$|P!+U4uCYx-^rdLv_LLh}eLd}9m?20m0;_@gKg2L6&>>gze z^&8CwDr;%gXqceq=!4PNAX-7cjAqTl)=5G&OM+c9ZPJcyY3pKYRe;U&7i45SZs*Il z9dVxeeN6r2q$HYh)nq`QZCO$;?*Pg<7UY@i^OBK?!5nFrPXhGsoa?dlj7=12JDgd@O6; zX0^=W-UtjlW?d{p4I#lNI|&|xASxc#7wEyUEE2czD#yB7prQq<$Fh$1+^Gc&M{Q2B zWwEjmOm7ba2SL^(bUrJvHjll=+2N+a96M3&H&(^j9HE z4A(}4#iH#8X=^CmJC%xxSac)zSSSk&4FMIQCz-A#sZ#*7JCbN+&7^fjSOU(Xw~APT z?`#?2jG04Mi?9HjOVP#H^qEJ)irHwWiG#&3feYxHVm1vITJuY=6SRn4EnyuzwL6Fw zGhHcRkrY%4yID=+N|}vXm$CwXt+wS_#b%TBC|xLJO)c+A4#|lN8XYl?MQ85QhOGSV z6ZC73*5fNp=m3-4@0}x4%e9fhvTO>j!0Xv@WP|L*<5*()KeJ zW=1m4)09=tW<0z}#>me#HQTXVVm1Y{J$ao(a%r<3uV|LWO0IZ$r9|GzdtQ&%Y>#k@ zm->a`Dp*3&3@s1%CfjD~Y;{OH0jIpW&4LH7oyyBd@houzl~h2l9i;UYtaIy^WlHN_ z`7f9>UAiWVzo9K(&@`##RXqIzwpsU(ZzY?^w!KS_jAz;C6sJ17L%=2FfTQ08ww=Yi zD$9+GGg*?gixH^F(D#@dzC~e=vF5$skZCOWS^{UeJGJVuFRRI_zI&pFC4gDbb52u) zYZ`NY(suor?YV0-=P}kOa-UXQk=EamE7tuo08c+0a2$Ay{ZPjYq3^*qYlXkXI!mT< z4#l_Wy(d_w)<<1bK;&I*-uR)@{>nR$I=y&0bmND#cqV3BN8hDkPqKu2Hp-C~uO@Di zZk!66kmwA8lrtlEtnCXEKBsa<2|YO8xb=#%4m`>F$ufN>v+-$KSqa?MZ;TDk|JK&6 z)0rT@ax;jt$|0>LZlqTxvtB4*c@>TvP)_?Q)>gVjRcw%S4^*)j>7K2^LJw}t6l~hT zoiqiLbhz86uzS*-k7tZ7E5!S!3l84bhzmvoV)NmOZms zGlZU=jVvM4UyAh(7b!eUd%v=3zAm{KI)HImA`HobkDGTjygl5=rlx&pJtW^nA9kwlr^9 z8on0XDx-ERU@6l5W&s;7-3J%q(_pyO3)x35dBV5VaU8$PT3f@UMJx-`lGhipW^spQ zSK*UBChdy_EYc><#uf?6QWmq;vdoml?38pZOY}OsEMZSbeBTn*O1eKSVW*}0-cmjN z^`-0+iGTEz-YZHq>nibX)ohfj<>9rnQbfI3%~D-a!F8h4r`b4}d*ajVed%^zrq@!w zjJ+Z8>~V>IxEi;);CifK?@9Og8up0tHnm>M+6TTZyGvg&*s`ROSV0rkvInF{ zomk68B)>1?=8cn; zx8+fBZO{Gwg5D#|w&*>waf>cZUuvxgpn1&8}@{6)0uwOIj%bKPk=VrI(ned8=Hcn=Ie!8w<~+;|=3eFR>;_dg~=N zC{R0Ll4}9^1UTjlSX*T8V9A|bCu4Q@*>x@?m~5~ADW?;*L`(}-?USm|I9zyxD;~5f zk%!YGjGO6~9qd=vQ%L%&-<#>uPS(87HZ5D6T-$1!i7umvS;B{U>7UCzDu4M)p@;Rv z!uF|N*`gU*tFXN(&+W2zv0l#21*S1$_b!$m?QDYEo!WKRHKOGQSmI^&AU;yEzs!DQ zA5WJLFtwv8yIGQpR(vj_6}4|SihPQ0?PjmzCfjR!*!{{UWZlb>eCsrT`s~FXWX&d8 zvX@1%1&3+-Ue+X5+i22Goz1SJE3@-XlWLnySCOdG94L2_eznPr<%B%oigQZaUo#F$ zD`U3d5X;lxa9q6-f@$O{ETHAT6!TWSV)PrL-|aJQh+ck$g)r^c;Fxv` zZE~=CSUQ(yXH-;zt2czV(fL`9BRgTEk$FbSa6`h6xGYM6!A#0 zs!j?7oG=F1Ha*7q?J_owReUk_9Cdw-?J@7ulcSeOQP7WgjWhV8 zMj7nzckLM|3u`{bvM6plY$$Cgs~b=jCmZ5-HkD!Abir;I&hy$-?H$Kt}5%PMMP zah32k4uY)9DQ_Q}?Dh11t+lw6^z%M;Omb}Bem1r?*pd7uyUZC1i+>9j&*0wu7V8h! zQT-Mh>gjdykd_}Gaa?{M=Vfl3UZBU+=kyCYryEH=Hk{&!oqPk)EVIk}Rl`#Hj=n+wPfKAyG)-WP-C0@Xu;%?i6Ade>8cqHLo9GbTxR6 zs49J`^FPnzU%x!(;Rk4lzeAL(0UzrPh;(Z}l`hu*%iONaiT{hLwaiuW4XQh}>E8%< zR(&V-#>K^jyTrdn7YZk*$D3!af~_m!rO;v{&Cia9hpNXkFy@qjXA;k4s&S?)poQYt)trZ=sG19+WK_E zDfZsdlzswITy&Noe${hEtr^wVq6|&6$Gw zoMtiR8M=o4x*k?~nx%TV?2TjVX&fh*_vtyGcKq};Ont&p9d*jIo3NiYA{-;mVpUP4 zTM%%4BfgSTU^=A90Qw$&;oLxF?+e>SUTa@uBCM@2t+Uvw`d~v&VDwwY!oKiv*!j^7 zkE>7@sux;omT-ae1U!_|eq~b~PyN6?!-*C?S-zm# zrUe(UK89^_Twq5m(RI}@n(sBhS6-QwEpn{*9ZT-21#fG8?$jmD?h-ba856c`4OtIi zbD1$=+t+Xkmt+as(L1ErP^FomcA#mSPDot=7PV$f*j6>1!X;V4cDhbA=nLOF=y8`B zlfTuhi&w4QlXk2jUFpj7k34tM+IP(1;&YYDh6#g^S|c2F2SQxA|1ERE|0r|S7@HJx zClCH#s``!$G{x1Ys>*8E`#bVbpI9{1A@V@T*;5zwo;N!2j)m2a7-apwX8D);V_o&v z81*}DUuSca=!{r4?%w=q*j!LMDe=^-r!6OuT zXQEi;yV*N)9El!0k+DygJ9?RU6NPQQP9C@2n3l>VNX)IqaQzGD|W&Y2oQj*3bV(zE2gW9M#F!fdYJZD|T%Gb@kz?(k=Jl z@#cMc+4E?X54W+;tLdzag&XAS9^1*6KW_a}XEc}AoPp7fcYXOc3VUl4ZT07G`#7{I z2390{(6NtM`F;E~c>ZJMgZLZpGzs8s*v9>Ie*jO5`c-2fcG;bzBBfl@G3&3>X=4D- z;BO+w*#JJ7U6@JsK;D|ajesSAe3DU(r|lp@LV{4uA$W>{^wQVJ0Gat(5MRfSz_T(K z#T~wV$njY)Kcpno)sV2y_4MP_>5f-Jd8|@z+KFcUBcUczP|Tu!l%WP1jB>OI=Y5o_ zdUX7w48zQO)-T){;T(^`_LIJFX!H-w2N7zRWSDs7xE_IT9@)+5j+91xgTii2r_W>f z1JX^7<(uI;PQ~(xg3X@g7}A(eQ{bV`oA9+VNN^nxoWnBHo}M_IM|hS)nX%jZ~tQD$@A%kzTXCc4rLH&9kL z=fP%lzgyOinqwS*_T(0P0{7Zy7(LqAim!7+A57vE_&oVo5>NKJXe^B#=GNTKt7;5f zR}4O`tNcfe()0)%(Ku!F^#3s8PDPzzcxEvXhf6q8w9?;O6>qwd!rMx{7S)%}k?zh^ z-dDOeQ+YF~=i>W;b(v}0SD8&c)A;w+*QXu?8?b)BhaE!dy2)*MD8***XzQO1M76Q* z*2k;+Siv`|0YuS$H%Uny_^Au;U zmNy&BC5+EzeVVOKhYFetv#E5=p6D4)v zVUiaoGI62dy-jo@lP4SD(KVVLF^)p?XE<^30vh??Cd%%>`=P|`9e8u2#5iA!ctbzB z6&}+O4A%>fzeAEm9eE?4cV}sHyp}klJVTC-JO;+8KLIn}dE1Yhdedjl6 zxx%f}D6|t#Y3{Z^#^oNH*(qVpLY%ks(7Pp_c()$fZ_69s8yZjV$Yah z>9nX)PfH<@-ARS%_;xI~GkA&XX-S=VrpA)1LwHNc%pF5DCOdX?<{v9wt6T%l>42U(om}PXFaY-k9a;su%R)6O3_zo=>Bv=6D z9#+#>xbFc%-S8dodjpI=l&|%kJML>BaAmw7>UaIg0Dl*zC$QGT1M1;{_3)s2cyK-3 z;EW8G^@Ym%h1F_;9yFB$8oA-`z<-w;{u}(V1xEVo@Jj*=T$U)i+`zBF@1Fi5aLG|4 z{#*E~WCAG{<3g+&i!0&EeyoBk`)!ufUEz#>*NJ}&*8}mNI^8ef%EG^eEAwA~E4%EX z6Tj-jZ^1RX0rF9$C6X1z0*ro+lYS>xa^Pk7gp*L=gQrf9e=;6}l{ z8*T#Jk|Y~Z;_+^HYQ1=g z%lNYp;9R(WqP%$7|3<(K1V|lY;81>bGFX61c`)MrfwywQgMrH~XbzC&NN%><9N7fH8h0D<$*<$Ug(`1;0nJE@*OWqaCJx z&sa^p;TnYZfxoW|2h0E=GHwDAN?p=h_P>#^Kl}p#17(C;_#pTP1BTQK9}4_lz z0AM&k{^=|$-_``rl0FMHs=9FBr1F``*fDwQYNWV-Yxz);qf$*wo)iO!}la#lF zRLV*dL4fRkgW}#8K5}?2fKW;zVgV92;(H=K4tU>Et2Sz+Mnw^(PXrD4jYlTQA(scp z2aE)a0*nS^0SW*{Bgeo$mKOfT!>ZzhRqG5-G(?VkgUR24o08mZs7k6FS(W^gOdkEL zZZl-tC5!{eKZ93g@J~Y%Wyi_RZs&$ezRM<^_S5>H%GA^u0LN88xkT`1R8XO#T3cm% zD*^7EHr^R;5HP`s8~G>J!zTfM5R-^Uq;8Z_&;kMO1wIDc;KU2Rs3Gg?7msw3gfEYk?z++ViOoyi%U;w}l7zd~VECZ|q zYy<28><7FD_ylkca1n4F;9YJt1p?v#tpFVX0|3JTd4O?%Cjhem^8t^8SOcmI&;M8M zZTzhY06`$)NfWHsWO(GC)DrH0Q=ACiFfHTlGt(e1Qb|Zzl(u&oz!2x@@Xr9u1Q>r( zOlAQh=>2Ovs>wolXP}5-aOXpYr3j9|sFZp|ssP!cp5*l>kE&V(?_$6bz)-V3ItnoG z?O0fhQkt2COtfk-QW01cO>MGhmQ z4G#EGrVD?y9{!pWFU9a40sk5BL#8+Iv-R+EF1$+5AiG1h*vOC$rb%sX;2FSW3k+PE zH+Ot1;@m6TRuA7^58r9vRZfa`839h*2Nb#!;1Arr!fy5O9rg0RGDxlE zoJk3RMPEo$e&$lc;x1=mDzpV9yobn=N?;-x8Fwv_wc!HwP zS2uXP!szA=Y=83Rbn6B-d^^|hKt<4?8lJ+`R2p1^-PM&fJPP+6U#a1DDV}t`hKDO= z`n!g=i1Z4x+hxJ_f}+xrvI=`?NnxS(k2iI`$=fIvD!Pe%XrFe}Xo;7N^n?!li=JOP?}p&QdS=Bc>OltqOgb!6u&_v zQSw6~or$`l0`czWkRK$Tr^i=Xn&j-uEgR^JGGS(N7+K8v-kvhDN2o^ zM?+OlmJnaPDm$9$b#a6^3jb+QANw;s)(VeFx8JWy^EHv7SS|b6@IK) zD;+tm3NMzIPgSaDWiHD{3rlia(0Ns)vb$_DNfae}h^9(Q$1o4vZeXnr>McI(Cmy3) zj| zUZ|>R3<5eh5YS#>9p{j}SWF5`O-av8OKaCQIW-v!X`9+-HHIPs`g_arG`{mYZ-L>m!i?Urs&zqc$WH$S~Jr>s2xZb}|6rYk+DOGnX?+H@3t z-uH}`86GI_XQy3?=s*#jL|dh&OpwWXwW6*aMRSj|;`|A{TWfwg)=_vUeP}=@k?hfT zfXqvW0z@o%CW@9yKdR~^rm_CLrAVjKBaKBc8&IjscWh%Zgbn>2^bdv& zdV&V0iRM(ZRQOQnM3M22b`PfW6GU60iK62_au31B?`ddN;3P4MDw>Efcmu!~inXHX z5tP|BC9`d2MtWLmyUg|(sVVK+raN+)3hzK=l82Yy1wdCtRY_7BPti4S^Eb7z5>S`(Y8POr)g# zB432^=?2zgB(NDuncfy(U;OOhOr=6%*|fwL%$%iYqn^)J+=e}NQk@typNB`ce)hS_ zw{$fN()d&t@gkq6d@ivk==HARAwFOEOvjE6!N^^eC5GS=*X+B+WGb>jS1zO}-Qma3 zH&g#^VmM!9q{rAdQ&M-NUo6v{#d8M8b`q~xWb_c{ai2Z%9&w0QE8ED{TU?eSzl-uT zb?q&Fm4iM@S>}L0RlyV7Q~HV;D())+6mr1tt0*fRi~50G{266BE$Jt&Q(SA|Y3|W2 zEyKRj2m*_pLC-2P^i=!vgjr9uN_mOS4HK7WCtQMCARWZXST6!Oh@@?Nuar{{?j`fRS~O_LrHks&E*Y3qjW7`$#oFF!Ny`;RzG~Kprorrdv8Y#RK%MRrf+9>s1 zGNqh_4vrlw{3v*|SQgt5g*%ladh!7kC?uTr&lO>`Z;^OhBbo0~ie0u_(8|W5r{!hk zOJ@Z(%JG2NKRy?O|CYZPNY7P>4!k&jBE`pw5TD)33(kc3c1a-FD}yyaK&(ezLfqvL@q~ zN*&519eZQ4NK{^>vy;UW%4^aYPJ62mgdZ5I5)+g+Xz&y<7H@{`p8~4)!W7w0^oDL$AgeE0#S6CIcWrSCUQc=CfvHm$xQvdxE-hc$lFAs-lt zxEaDi2c|>4KeAoK;{}OR)5OExZ!7ofeS$V~(Syz%hV?&8i>8aQ{D?A=0>JINzs|nc`7AulVsy*wqi| z$}G{@V|0&hZS6;Of2H()L~CY=!T8_>(cSnlWr)<)O(|=Z2%*y1B8(qb^62Vpkx6gQ zhHX1RmuHJU{69*6J+N|)2~mp%|_)OV3+Z#kpvZBS;b_*vy~mnqJoW>1N5eh&0&rh4sS5s8a&K1;-0?{Abz zdVgfluKS@2K3F1p@^9;K_&bfm{Cj1r%Vc*~exR(UL>d1t1TMQ+JVJrhB1*YHt*bE$ z`%$5Z)#6$HlQKd#4x^qH0qkdm&OI%L(nk`xs3Cd$7ZeJMF^FiHh~k%EmNm=hu^hJL zvQntWl`O~9`iepemy0&+szPrrMkCS*<{X@xv z=#G0vG^1g^iH_`=LRl+BfA*(Bi&u#I*mZ?|Sb-VLUkZ(R2IT&YXD7!#BNhc`YD*7$ zc}3X`rGw6$WC~~tnSOsI>{^XNXIF{`*iD5ppB35smd@8iI`FJ$#cwMEbS_t{5D~Dc zomat*O=S;I$`$c|Wf~)`!-fy2&?#%OS`@%&EM6^Ay=Sm_P-49?rK4TGB9yXrU~=-q zYVo5o({X%_l#E$)cC9$cXR}`QXO$P96Cu8H*m9IE3n)*v)3FyZXj`olC3q`h)jH9` zYaSaTThqUs4(`LUC1}0qsLZE<>qRkNz#gT+>oLLkWxYs&U5(u!=J7==M^@9Cwtj-S z(2WfukS}IsGN>hW+z2^Y!e-F85>ZKiZxmM8R?8+a+~X;BpPVi&eH$&9vPpEpYZLo7 zVNgHKlIYDXP{Uc9MG#-cis`^6Xv$5SMJ!*=+RL~m)a6y-rzT@2OLTj)Si@JaG@7~{ z+IY}LVF|;;wzweIKB1suw0(3*$=GMK26e|rks^ZD{t4~*_w(W&WhL2O5WV@cY?Mo5 z-)&ySvSs6k)A@Hq8)~@)UA3BJ=!JtK51I(t_!`z!58A#3{9a4@wulV=94HU?1C(F> z0|q4aMWLG4vF;ixWk*HQ!EKn54}B48WIao+rI(hxD17+_)=Xc6?R!yJ@GBYby(sSG zn^+4yu;W%F+02}4wozM!AAg>;)Zzlu>~?!uUZFitU$4DTKis*F+rnDwWnI}S0-xmKI@&+9BxR!JvbgfZQ`69erHI=B-n?0pPZPGwg~d7%=Q! zpz;yLJH%7`WA-q8@S;rV5MkcO*hIb1WLokzEXKfBF@ZeJo}_@+#3VIsRDQ(?@_S88 z=l@~T$od)<`hKs&ihY7Y;*%jlonIH3;#0O-(b3hfi^nj%&VB>KwDTKyRONH&@2Q;B zgFcc$r|8NXm{5E{qxXpcuw#yWVv_O|rR)cGnx^c>JmzcmkWN*aU3OnH3Oj&4jC&Ib z}DY7kRnYMBAK)G9rMs)5?p<2#05S75rGi~dHf5ZAy>z`l-90x=a|CWuWtB1sW z)ajrIQof@>2SpM8o)zkWA%`$pe}LALNqo{r7FL#z?v`o)uQSAwuGs*DTwrwNkjVA^ zk+s+99!%q&717Io!&L9dw}h4dgey zEQiG`@5}6;lOTSDEupDL#56gr@n=^VO*tZ#@Lw5zo$`q2CObccX1#+k@f(}(78Lfb zh~U4oSu&_ImA(rR`2%X%z&g^PlhBKgzArr3HLc7pGLfM0_eEE&D*h*1;7YEA_v6=D zC0#lUA=QS_lJ`YF_7~Qk?~4NVH>2Jkh@k;Dm``rbhyuG)3Dq##{(;EnH`ya{wvtH$ zJ`|1lE%u;3ciH!$2x7Mxo%m4n?>vQjXxtq?l?!Cgo5ocQpUzc#4=9uwJVM)wD<~`& zk!#N{F0U-hhcmvQtfY7*b@@od2F~JY&IoKP&gLHAYwm=+IaKnIh_KD&;d%LTORyCC zclHXol(i!Ps2n+uTir_4DxA;#-GU@33+Vlip!^onRg~6e5f623TWUq+m6ep*OC}UA z=D}J7NGL0rplxf)_$53TTb>1_srJHx@(O!pTB?0yS$_Uf>~H$i@Q6g2^(h{OiA8=+ zQMuijyP&wVvO>-_5Liu1KE~$)Pt&fCMGU_1J^rx>;mdhbve)ngRpux1KPK+Nm-QWv ziAL?7;a*Y&+vPtid89^EQGUhfk~|kt_7T|Y8~ZHxay4ZYEj)(7x|((#gWg(${oP$R zaa(mQPmrEQW{o$`@s=83onFI_bY|RM~R<)IT%W_LJ z9&Y0yI->VxPM(ut+j(ehxJI}$;w5fz<}RBee@IsI_Y)IlUG^#hF<=peca1#FU+kd%PB73&&^T*AWda& z%4Wk`1;4%QfR+mv-VSn41Q(;KLtK=PC>#40s@C>#-{$7LLfw0q`<9mFm*yAe*|n%6 zTu1?YhkN7|O3%C8L+);uF zSTc$#BId$rdvI{@w^(`37O6VNrhg0LItO(I{Do!F zTsrZs7=$kokTROOe1~42k0Tttll-t&tN0Ewx_}mbhg1t)smy-)6HD`RE9@h43JUY{ z7RiVdz8G?$O|wRQkLfE+|M#G5sc^10+tp4oE`1MKdrD+T=EPI>53uH~e!#q=8j@!O z`NLwA{vi6pV!ZhS)>_L&Urlr)+U1qyj4CP4baqQhGKo%F+v(6Rn8&UCuL$&BA?|HZ z7(%G#zt~`ThP*C7idJH}@YS!_Tzr=1T)+|0D*E7pNaL%)0IeB`KZ+plH3t3pwe)Ap zh_yyU``U=rh`?KxyM7d%m34IaM=TZBQ>&lgY@mXlFst22ul)oO+Jw2Q?N^Zouzzv3fZV9}5|;r>T4f^K@-98jz#v7IYVjD@1iY>f@=I-+$HCI-kxoH z4IJ1v+F4fEA2@DkG!Z{=3)#>9Lp;o5mTPy?X~{Lv7dK2N0GWbaOEDk% zYTgM*S&J0wjb-{Ey8b8jh8j20k(lekn>C4cAs#C1TGOF=Zg99G zYnH87)ALK5Jzlvkd|C7EdUV)dpteP}hBzMnODN2D0W-;SZXDe!jZ`YWA;KfGl`0eW z_A)7qGa>&}rBw0q@=W%o)i*@rDsyA~@VZvZ{b5wpoF92OU*;0}aE{vsN_ z;a~wwK8~oTi{myPfL{c3#-U~Mmoo5RkQyMIJ`b1A)8#WupQ(*kX&K$F5v}~NcU+(F zdnMtlv$sh}md!~qDJCTqx7w666E`Vsr7s=#&XjiYpZ4;f4E$5u8reuMTn({FGSGOT zdU(oXdy&y1hU2D~C@d(>hijH@(%Ae-+{9IFa$?3qkCAwKRZ*&Md4)xK_$sK-EN7N- z{wrgnJfakkLsxF%NYbprkDHgC_!ogVsJw}3tri(>W-eS*hQ;Vt2BP1VBEw13;mnDahywDlII#-e(GcMMY_ zp7IHyCBdp-CJzd@jhDtfvD4GU)^esO4aCcP_I^Qf3DTjUq>a{LS;?(aTPFu9)#EHdERa>l`Gm4SPMs3e zMjZDRNa3j39;;e_sbon|Ba@Q@J@hhi3k%xFNv^XDPi=A2L59i+aIrJkOiF|r8X4#n zqE+mSM&-_UZ#u}-SUMl623POI4@d_3$V%fBU%DNk#`jJR^z%p+CZ_nynRH3nBoi86 zE2<_%mlBiWUuV5(hUPYz86+mm0}^EtuI{#IQC?aUeZOe0j8xX^md@ z^Duh?_n`-c8sp&$(U)ppsIiJ4tr2R1;!lU*47DOB%V3JPJ6*}9aE!bN47^B;yC@91 zXpFi>7<4fhbFmn5acGq((CTSXvJ`Ddg)1hH=5i?byIt$RZ;c`-UaQ;`=O|Fs03H$$ zplhC_ibZEU^U0iWxYNnwy#amS)8VlJc zCbdbDr>bN@3mE*vO_*qU-CMOpHZ`^O^kt>t29Hf`Jcenc$lEdrP*48ajT%a}sH%4} zXL-$&9bGNzMUJaN0e)(9Xi%V&eIqN2bCdKbFMd!j$4?Ck38}-Z|G)IbVqY~B*Jfmw zuy80kUp3gA%p+vyM4{nPnJTQ*Cy9Q!CAfc84DFO8uYD;$;l61L6YhSMmsFPJ=C_tR z8cJQ1F5kp7mQ!Z18XqU>#;J8tj|@5e;o_J%W+;55U6%!-rZ$_ z^y)W!{1l5+(Xl>tOYp6W`q7C%we4O0+QQrFB^9iuYXZETUYLhK!w(%3L~Vl94lKAM zB?qZ7v@=09)8!B~goQXmj?0jbgVd0i&;}G{n6U~D*ILUW+}jhWw8)%?mvp$0iV zj!>%=rG|hiXU=KqVD1Y7R;{ETdzQLUWJoL+ufqpo%uSgnQ zQeKfHbzYEPMb6m#T0Gc8?~o8RcXWR4SmjTZw{(2eNX=wbnu3pT&6pzf`BIc?ZWQgK z?ejS&HjV1eb7K5l)M=?={av^|ABtBsy_2A7TbhZUPPN}yNKN0wr!2p1MP*rWl6Fkq tRMnJxGth|roaR0wD-oBk$F)$k&nWNGs7;}-, done: Vec, + renaming: Renaming, } impl Replay { @@ -189,6 +195,7 @@ impl Replay { Replay { lines: trace.0, done, + renaming: Renaming::default(), } } fn pending(&self) -> impl Iterator { @@ -204,14 +211,19 @@ impl Replay { pub fn next_host_line(&mut self) -> Option { self.take(|line| line.is_start_line()) } - /// The next host line about `key`, e.g. the `Return` of a STARTED call, which the import's - /// mock waits for, or a copy of a stream, which a job performs. + /// The next host line about `key`, in replay's labels, e.g. the `Return` of a STARTED call, + /// which the import's mock waits for, or a copy of a stream, which a job performs. pub fn take_host_line(&mut self, key: &LineKey) -> Option { - self.take(|line| line.by == Party::Host && line.is_about(key)) + let key = match key { + LineKey::Handle(label) => LineKey::Handle(self.renaming.to_trace_label(label)), + key => key.clone(), + }; + self.take(|line| line.by == Party::Host && line.is_about(&key)) } fn take(&mut self, matches: impl Fn(&Line) -> bool) -> Option { let (idx, line) = self.pending().find(|(_, line)| matches(line))?; - let line = line.clone(); + let line = self.renaming.to_replay(line); + self.renaming.end(&self.lines[idx]); self.done[idx] = true; Some(line) } @@ -227,24 +239,35 @@ impl Replay { panic!("the trace has `{line}`, which replay didn't reach"); } } - /// Checks a guest line against the trace, and returns the host lines that answer it at once, - /// e.g. the `Return` of an import call. A guest line is matched by its task, for the lines of - /// a call, and otherwise as the trace's next guest line. + /// Checks a guest line against the trace, up to the pairing of labels, and returns the host + /// lines that answer it at once, e.g. the `Return` of an import call. A guest line is matched + /// by its task, for the lines of a call, and by its handle, for those of a stream, a future + /// or a resource. /// /// Panics on a mismatch, with the expected and the actual line. pub fn check(&mut self, actual: &Line) -> Vec { assert_eq!(actual.by, Party::Guest, "`{actual}` isn't a guest line"); let task = actual.event.task(); - let found = self.pending().find(|(_, line)| match task { - Some(task) => line.event.task() == Some(task), - None => line.by == Party::Guest, + let key = match &actual.event { + Event::Write(chunk) | Event::Read(chunk) => Some(&chunk.stream), + Event::Value(value) => Some(&value.future), + Event::Drop(label) => Some(label), + _ => None, + } + .map(|label| LineKey::Handle(self.renaming.to_trace_label(label))); + let found = self.pending().find(|(_, line)| match (task, &key) { + (Some(task), _) => line.event.task() == Some(task), + (None, Some(key)) => line.by == Party::Guest && line.is_about(key), + (None, None) => line.by == Party::Guest, }); let Some((idx, expected)) = found else { panic!("the guest did `{actual}`, which the trace doesn't have"); }; - if expected.by != Party::Guest || expected.event != actual.event { + let expected = expected.clone(); + if expected.by != Party::Guest || !self.renaming.matches(&expected, actual) { panic!("the trace has `{expected}`, but the guest did `{actual}`"); } + self.renaming.end(&expected); self.done[idx] = true; let Some(task) = task else { return Vec::new(); @@ -259,8 +282,10 @@ impl Replay { if !line.answers_guest() { break; } + let line = line.clone(); + answers.push(self.renaming.to_replay(&line)); + self.renaming.end(&line); self.done[idx] = true; - answers.push(line.clone()); if !matches!(line.event, Event::Started(_)) { break; } @@ -399,6 +424,74 @@ mod tests { replay.finish(); } + #[test] + fn replay_renames_labels() { + let session = |name: &str| Label { + table: Table::Exports, + name: name.to_string(), + }; + let (t1, t2) = (task(Table::Exports, 1), task(Table::Exports, 2)); + let trace = Trace(vec![ + line(Party::Host, call(t1, "new", &[])), + line(Party::Guest, ret(t1, Some("#session-3"))), + line(Party::Host, Event::Drop(session("#session-3"))), + line(Party::Host, call(t2, "new", &[])), + line(Party::Guest, ret(t2, Some("#session-3"))), + ]); + let mut replay = Replay::new(trace.clone()); + replay.next_host_line(); + // Replay's table numbers the session differently. + replay.check(&line(Party::Guest, ret(t1, Some("#session-1")))); + let drop = line(Party::Host, Event::Drop(session("#session-1"))); + assert_eq!(replay.next_host_line(), Some(drop)); + // Once dropped, both labels may name another session. + replay.next_host_line(); + replay.check(&line(Party::Guest, ret(t2, Some("#session-2")))); + replay.finish(); + } + + #[test] + fn replay_ends_labels_handed_on() { + let (t1, t2, t3) = (task(Table::Exports, 1), task(Table::Exports, 2), task(Table::Exports, 3)); + let trace = Trace(vec![ + line(Party::Host, call(t1, "new", &[])), + line(Party::Guest, ret(t1, Some("#session-3"))), + // The host hands the session back, which frees its number. + line(Party::Host, call(t2, "close", &["#session-3"])), + line(Party::Guest, ret(t2, None)), + line(Party::Host, call(t3, "new", &[])), + line(Party::Guest, ret(t3, Some("#session-3"))), + ]); + let mut replay = Replay::new(trace); + replay.next_host_line(); + replay.check(&line(Party::Guest, ret(t1, Some("#session-1")))); + let close = replay.next_host_line().unwrap(); + assert_eq!(close.event, call(t2, "close", &["#session-1"])); + replay.check(&line(Party::Guest, ret(t2, None))); + replay.next_host_line(); + // The recording reused the number, and replay didn't, or the other way round. + replay.check(&line(Party::Guest, ret(t3, Some("#session-2")))); + replay.finish(); + } + + #[test] + #[should_panic(expected = "the trace has `guest: return t2 #session-3`")] + fn replay_checks_labels() { + let (t1, t2) = (task(Table::Exports, 1), task(Table::Exports, 2)); + let trace = Trace(vec![ + line(Party::Host, call(t1, "new", &[])), + line(Party::Guest, ret(t1, Some("#session-3"))), + line(Party::Host, call(t2, "get", &[])), + line(Party::Guest, ret(t2, Some("#session-3"))), + ]); + let mut replay = Replay::new(trace); + replay.next_host_line(); + replay.check(&line(Party::Guest, ret(t1, Some("#session-1")))); + replay.next_host_line(); + // The trace's `#session-3` is replay's `#session-1`, not another session. + replay.check(&line(Party::Guest, ret(t2, Some("#session-2")))); + } + #[test] fn started_then_return() { let c1 = task(Table::Imports, 1); diff --git a/crates/trace/src/renaming.rs b/crates/trace/src/renaming.rs new file mode 100644 index 0000000..ee5dff2 --- /dev/null +++ b/crates/trace/src/renaming.rs @@ -0,0 +1,306 @@ +//! The pairing of a trace's labels with replay's. A label is a handle's number in a generated +//! component's table, and the tables of the recording and of replay differ: besides the values +//! the trace labels, they hold waitable sets and subtasks, which each mode makes its own. So +//! replay compares lines up to renaming: it pairs each label of the trace with the label replay +//! sees for the same value, one to one, per table, from the first line that has both, and requires +//! every later line to agree. A pair ends when its handle leaves the table, which frees its number: +//! at a `Drop`, a future's `Value`, or when the host hands it on, owned, to the guest, in an export +//! call's arguments or an import's result. The rule is the same for every label, whatever it names. +use std::collections::HashMap; + +use crate::{Event, Label, Line, Party, Table}; + +#[derive(Default)] +pub(crate) struct Renaming { + to_replay: HashMap<(Table, String), String>, + to_trace: HashMap<(Table, String), String>, +} + +/// A WAVE value, cut into its labels, e.g. `#counter-1` or `#borrow-counter-1`, and the text +/// between them, which may have a `#` in a string or a char. +#[derive(Debug, PartialEq)] +enum Token<'a> { + Text(&'a str), + Label(&'a str), +} + +fn tokens(wave: &str) -> Vec> { + let bytes = wave.as_bytes(); + let mut tokens = Vec::new(); + let (mut start, mut idx) = (0, 0); + while idx < bytes.len() { + match bytes[idx] { + quote @ (b'"' | b'\'') => { + idx += 1; + while idx < bytes.len() && bytes[idx] != quote { + idx += if bytes[idx] == b'\\' { 2 } else { 1 }; + } + idx += 1; + } + b'#' => { + if start < idx { + tokens.push(Token::Text(&wave[start..idx])); + } + let end = idx + + 1 + + bytes[idx + 1..] + .iter() + .take_while(|byte| byte.is_ascii_alphanumeric() || **byte == b'-') + .count(); + tokens.push(Token::Label(&wave[idx..end])); + (start, idx) = (end, end); + } + _ => idx += 1, + } + } + if start < bytes.len() { + tokens.push(Token::Text(&wave[start..])); + } + tokens +} + +/// A label, e.g. `#borrow-counter-1`, as its borrow, and the handle it names, `counter-1`. +fn handle_of(label: &str) -> (bool, &str) { + let label = label.trim_start_matches('#'); + match label.strip_prefix("borrow-") { + Some(handle) => (true, handle), + None => (false, label), + } +} + +/// The table a line's labels are in: its task's, or its handle's. +fn table_of(event: &Event) -> Table { + match event { + Event::Write(chunk) | Event::Read(chunk) => chunk.stream.table, + Event::Value(value) => value.future.table, + Event::Drop(label) => label.table, + event => event.task().unwrap().table, + } +} + +impl Renaming { + /// Pairs the trace's label `trace` with replay's `replay`, or returns false if either is + /// paired with another. + fn pair(&mut self, table: Table, trace: &str, replay: &str) -> bool { + let ((trace_borrow, trace), (replay_borrow, replay)) = (handle_of(trace), handle_of(replay)); + if trace_borrow != replay_borrow { + return false; + } + let key = |label: &str| (table, label.to_string()); + match (self.to_replay.get(&key(trace)), self.to_trace.get(&key(replay))) { + (Some(paired), _) => paired == replay, + (None, Some(_)) => false, + (None, None) => { + self.to_replay.insert(key(trace), replay.to_string()); + self.to_trace.insert(key(replay), trace.to_string()); + true + } + } + } + /// Ends the pair of the trace's label `trace`, whose handle has ended. + fn unpair(&mut self, table: Table, trace: &str) { + let (_, trace) = handle_of(trace); + if let Some(replay) = self.to_replay.remove(&(table, trace.to_string())) { + self.to_trace.remove(&(table, replay)); + } + } + /// Whether the trace's WAVE value `trace` is replay's `replay`, up to the pairing, which it + /// extends. + fn unify(&mut self, table: Table, trace: &str, replay: &str) -> bool { + let (trace, replay) = (tokens(trace), tokens(replay)); + trace.len() == replay.len() + && trace.iter().zip(&replay).all(|tokens| match tokens { + (Token::Text(trace), Token::Text(replay)) => trace == replay, + (Token::Label(trace), Token::Label(replay)) => self.pair(table, trace, replay), + _ => false, + }) + } + /// `wave` with its labels renamed by `rename`, of the handle a label names, if it has one. + fn rename(wave: &str, rename: impl Fn(&str) -> Option) -> String { + tokens(wave) + .into_iter() + .map(|token| match token { + Token::Text(text) => text.to_string(), + Token::Label(label) => match (handle_of(label), rename(handle_of(label).1)) { + ((true, _), Some(handle)) => format!("#borrow-{handle}"), + ((false, _), Some(handle)) => format!("#{handle}"), + (_, None) => label.to_string(), + }, + }) + .collect() + } + fn to_replay_wave(&self, table: Table, wave: &str) -> String { + Self::rename(wave, |handle| self.to_replay.get(&(table, handle.to_string())).cloned()) + } + fn to_replay_label(&self, label: &Label) -> Label { + Label { + table: label.table, + name: self.to_replay_wave(label.table, &label.name), + } + } + /// Replay's label, e.g. a job's for the stream it keeps, as the trace's. + pub(crate) fn to_trace_label(&self, label: &Label) -> Label { + let name = Self::rename(&label.name, |handle| { + self.to_trace.get(&(label.table, handle.to_string())).cloned() + }); + Label { + table: label.table, + name, + } + } + /// A line of the trace, with the labels replay has paired its own with renamed to those, so + /// that replay can perform it, e.g. drop the guest's resource the trace's label names. + pub(crate) fn to_replay(&self, line: &Line) -> Line { + let table = table_of(&line.event); + let wave = |wave: &String| self.to_replay_wave(table, wave); + let event = match &line.event { + Event::Call(call) => Event::Call(crate::Call { + args: call.args.iter().map(wave).collect(), + ..call.clone() + }), + Event::Return(ret) => Event::Return(crate::ReturnValue { + value: ret.value.as_ref().map(wave), + ..ret.clone() + }), + Event::Write(chunk) => Event::Write(crate::Chunk { + stream: self.to_replay_label(&chunk.stream), + items: wave(&chunk.items), + }), + Event::Read(chunk) => Event::Read(crate::Chunk { + stream: self.to_replay_label(&chunk.stream), + items: wave(&chunk.items), + }), + Event::Value(value) => Event::Value(crate::FutureValue { + future: self.to_replay_label(&value.future), + value: wave(&value.value), + }), + Event::Drop(label) => Event::Drop(self.to_replay_label(label)), + event => event.clone(), + }; + Line { + event, + ..line.clone() + } + } + /// Whether the trace's line `trace` is replay's `replay`, up to the pairing, which it extends. + pub(crate) fn matches(&mut self, trace: &Line, replay: &Line) -> bool { + if trace.by != replay.by { + return false; + } + let table = table_of(&trace.event); + let mut label = |trace: &Label, replay: &Label| { + trace.table == replay.table && self.pair(trace.table, &trace.name, &replay.name) + }; + match (&trace.event, &replay.event) { + (Event::Call(trace), Event::Call(replay)) => { + trace.id == replay.id + && trace.is_async == replay.is_async + && trace.func == replay.func + && trace.args.len() == replay.args.len() + && trace + .args + .iter() + .zip(&replay.args) + .all(|(trace, replay)| self.unify(table, trace, replay)) + } + (Event::Return(trace), Event::Return(replay)) => { + trace.id == replay.id + && match (&trace.value, &replay.value) { + (Some(trace), Some(replay)) => self.unify(table, trace, replay), + (None, None) => true, + _ => false, + } + } + (Event::Write(trace), Event::Write(replay)) + | (Event::Read(trace), Event::Read(replay)) => { + label(&trace.stream, &replay.stream) && self.unify(table, &trace.items, &replay.items) + } + (Event::Value(trace), Event::Value(replay)) => { + label(&trace.future, &replay.future) && self.unify(table, &trace.value, &replay.value) + } + (Event::Drop(trace), Event::Drop(replay)) => label(trace, replay), + (trace, replay) => trace == replay, + } + } + /// Ends the pairs of the handles that leave the table with the trace's line: a dropped one, a + /// future whose value was taken, or those the host hands on, owned, to the guest. + /// + /// The party of a call or return says which way its handles move. A host line hands them from + /// a generated component to the guest: an export call's arguments, or an import's result, whose + /// labels end. A guest line hands them to a generated component: an import call's arguments, + /// or an export's result, which come in as new handles, with new labels, which `unify` pairs. + /// The guest's own handles aren't labels. + pub(crate) fn end(&mut self, line: &Line) { + let table = table_of(&line.event); + let mut hand_on = |wave: &str| { + for token in tokens(wave) { + if let Token::Label(label) = token + && !handle_of(label).0 + { + self.unpair(table, label); + } + } + }; + match &line.event { + Event::Drop(label) => self.unpair(label.table, &label.name), + Event::Value(value) => self.unpair(value.future.table, &value.future.name), + Event::Call(call) if line.by == Party::Host => call.args.iter().for_each(|arg| hand_on(arg)), + Event::Return(ret) if line.by == Party::Host => ret.value.iter().for_each(|value| hand_on(value)), + _ => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tokens_skip_strings_and_chars() { + assert_eq!( + tokens(r#"(#counter-1, "a #counter-2 \" #x-3", '#', #borrow-pollable-4)"#), + [ + Token::Text("("), + Token::Label("#counter-1"), + Token::Text(r#", "a #counter-2 \" #x-3", '#', "#), + Token::Label("#borrow-pollable-4"), + Token::Text(")"), + ] + ); + assert_eq!(tokens("#stream-7"), [Token::Label("#stream-7")]); + assert_eq!(tokens("42"), [Token::Text("42")]); + } + + #[test] + fn unify_pairs_one_to_one() { + let mut renaming = Renaming::default(); + let table = Table::Exports; + assert!(renaming.unify(table, "ok(#session-3)", "ok(#session-1)")); + // The pairs hold, both ways, and for borrows. + assert!(renaming.unify(table, "#borrow-session-3", "#borrow-session-1")); + assert!(!renaming.unify(table, "#session-3", "#session-2")); + assert!(!renaming.unify(table, "#session-4", "#session-1")); + assert!(!renaming.unify(table, "#session-3", "#borrow-session-1")); + // Per table. + assert!(renaming.unify(Table::Imports, "#session-3", "#session-3")); + // Until the handle ends. + renaming.unpair(table, "#session-3"); + assert!(renaming.unify(table, "#session-3", "#session-2")); + assert!(!renaming.unify(table, "\"a\"", "\"b\"")); + } + + #[test] + fn rename_to_replay() { + let mut renaming = Renaming::default(); + assert!(renaming.unify(Table::Exports, "#session-3", "#session-1")); + assert_eq!( + renaming.to_replay_wave(Table::Exports, "[#borrow-session-3, #session-5]"), + "[#borrow-session-1, #session-5]" + ); + let replay = Label { + table: Table::Exports, + name: "#session-1".to_string(), + }; + assert_eq!(renaming.to_trace_label(&replay).name, "#session-3"); + } +} diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 7b43695..94beb90 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -203,7 +203,8 @@ number in that table, e.g. `#fields-3` or `#stream-7`. replay, a mock carries the trace's label. The mock's own handle can't label it: it moves on with the mock, which frees its number, and a borrow the guest passes back comes in as the mock itself, without a handle. Fuzz mode records the end of every mock, also the host's drops, which - record mode can't see. A resource of the guest, which the exports component wraps, is in the + record mode can't see; they take no step, as the guest doesn't see them, and replay has nothing + to do for them. A resource of the guest, which the exports component wraps, is in the exports table, e.g. the host's drop of a session, `drop #session-1 (exports)`. - **Streams and futures** are labeled by the handle of the end the proxy received, which it keeps for the stream's lifetime: the host's reader in an import's result or an export's arguments, the @@ -248,14 +249,22 @@ Limits: reader of it just doesn't see the two labels as one stream. The [core-level shim](#portability) could carry the label through, as the end keeps one handle in the guest's table from when it arrives until the guest passes it on. -- **Replay can't check labels.** Replay's tables differ from the recording's, with mocks instead - of the host's ends and the pumps, so it can't recompute the labels, only use the trace's. +- **Replay compares labels up to renaming.** A table holds more than the values the trace + labels: waitable sets and subtasks, which each mode makes its own, so the same value can have + another number in replay. Replay pairs each of the trace's labels with the one it sees for the + same value, one to one, per table, from the first guest line that has both, and requires every + later line to agree. A pair ends when its handle leaves the table, which frees its number: at a + `Drop`, a future's `Value`, or when the host hands it on, owned, to the guest, in an export + call's arguments or an import's result; the rule is the same for every label. The lines replay + performs it renames to its own labels, e.g. the guest's resource a host `Drop` names. + So replay checks that the labels are used consistently, not their numbers. - `list` items are WAVE lists for now; p3.md's byte strings (`b"…"`) would be shorter. Mocks already use a counter. Extending it to every handle, shared by the two generated components, would make a number never reused, and the same in record and replay, so that replay -could check each label, without a `table`; streams, futures and error-contexts would map each -handle to its number with `set_handle_label`. It has the same limit for ends that come back. +could compare labels exactly, without renaming, and without a `table`; streams, futures and +error-contexts would map each handle to its number with `set_handle_label`. It has the same limit +for ends that come back. ## Recording @@ -404,7 +413,10 @@ trace, the two results could reach the guest before or after a different one of - **Deterministic runtime choices**, see [Portability](#portability). - **The same executor in record and replay**, with only the leaves different: host calls in record, futures that the trace makes ready in replay. Its work in a step depends only on its - state and the trace's lines for that step. + state and the trace's lines for that step. The same goes for `start`: fuzz mode's waits for + the steps it chooses before an export call, through `wait-until-step`, as replay's waits for the + trace's. When fuzz yielded instead, the two made different tasks, and a sixth of the spike + guest's traces replayed its two results in the other order. - **Steps in replay where the host woke the executor in the recording.** Replay has no host, so a job that waits for a later step makes the executor yield, and come back as the next step. wasmtime runs the ready tasks first, as it did before the host's completion in the recording. @@ -523,12 +535,13 @@ executor runs both. - **Divergence** can also show up as a stall, when nothing can reach the next line; wasmtime then traps with "deadlock detected", and the recorder prints the line it was waiting for. - **Recorder interface** (`assets/recorder.wit`): `types` mirrors the Rust types; `record` - records a line; `replay` has `next-host-line`, the next line the host performs on its own, e.g. - an export call, and `check`, which checks a guest line, and returns the host lines that answer - it at once, e.g. an import's `Return`. Waiting for a step is the executor's, not the recorder's; - phase 3 adds a way for the executor's jobs to take the host lines of their task or handle. The - bundled recorder (`assets/recorder*.wasm`) and the host recorder of `run` - (`--use-host-recorder`) implement it, with `Replay` of `crates/trace`. + records a line; `replay` has `next-host-line`, the next line `start` performs, an export call + or the host's drop of a resource of the guest; `take-host-line`, the next host line about a task + or a handle, e.g. the `Return` of a STARTED call, which its mock waits for; `check`, which + checks a guest line, up to renaming, and returns the host lines that answer it at once, e.g. an + import's `Return`; and `finish`, which checks that replay reached every line. Waiting for a step + is the executor's, not the recorder's. The bundled recorder (`assets/recorder*.wasm`) and the + host recorder of `run` (`--use-host-recorder`) implement it, with `Replay` of `crates/trace`. ## Correctness @@ -641,7 +654,9 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla 2. **Trace format** in `crates/trace`, and the recorder interface: done, for p2 record and replay. 3. **Replay**, tested on traces that fuzz mode writes in this format, with the checks of - [Correctness](#checking-the-implementation). Fuzz mode writes the format: done. + [Correctness](#checking-the-implementation). Fuzz mode writes the format, and replay + replays calls and steps, RETURNED and STARTED: done (`make test-fuzz-replay`). Streams and + futures remain. 4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. 5. **Portability across runtimes**: the profile proposal, or the shim. diff --git a/src/codegen/dialog.rs b/src/codegen/dialog.rs index 5ca0dbd..276a8be 100644 --- a/src/codegen/dialog.rs +++ b/src/codegen/dialog.rs @@ -71,14 +71,28 @@ impl State { arg_names, arg_wit_tys, arg_tys, + has_ret, ret_async, .. } = func; - // The streams and futures the guest returns, whose reading the user decides. - let ret = if *ret_async { - quote! { Dialog::consume(#call, 0); } + // The result, as the mocks print theirs; then the streams and futures in it, whose + // reading the user decides. + let print_ret = if *has_ret { + quote! { + proxy::util::dialog::print(0, &format!("ret: {}", wasm_wave::to_string(&ToValue::to_value(&res)).unwrap())); + } + } else { + quote! {} + }; + let consume = if *ret_async { + quote! { Dialog::consume(res, 0); } } else { - quote! { let _ = #call; } + quote! { let _ = res; } + }; + let ret = quote! { + let res = #call; + #print_ret + #consume }; quote! { { diff --git a/src/codegen/fuzz.rs b/src/codegen/fuzz.rs index 125c031..d890285 100644 --- a/src/codegen/fuzz.rs +++ b/src/codegen/fuzz.rs @@ -110,58 +110,34 @@ impl State { .map(|func| { let ExportFunc { display_name, - func, arg_names, arg_tys, - call_params, - has_ret, is_async, .. - } = func; - // An async call that blocks gives the host a subtask, which the guest started. - let call = if is_async { - quote! { call_export(__id, #func(#(#call_params),*)).await } - } else { - quote! { #func(#(#call_params),*) } + } = &func; + let args = quote! { + let mut __params: Vec = Vec::new(); + #( + let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); + __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); + )* }; - let value = if has_ret { - quote! { Some(wasm_wave::to_string(&res.to_value()).unwrap()) } - } else { - quote! { None } + // The guest, and the host's background work, run a few steps before the call, + // as they do before replay's, in the steps the trace has. + let before = quote! { + let __id = trace_task_id(Table::Exports); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after(proxy::conversion::executor::current_step() + __runs).await; + proxy::recorder::record::record(&Line { + at: Some(__at), + ..trace_call(Party::Host, __id, #display_name, #is_async, __params) + }); }; - // The arguments the call borrowed are dropped at the end of the inner block, which - // are the host's drops, not the guest's. - quote! { - { - let res = { - let mut __params: Vec = Vec::new(); - #( - let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); - __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); - )* - let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); - proxy::recorder::record::record(&Line { - at: Some(__at), - ..trace_call(Party::Host, __id, #display_name, #is_async, __params) - }); - let res = #call; - proxy::recorder::record::record(&trace_return(Party::Guest, __id, #value)); - proxy::conversion::executor::host_drops(true); - res - }; - // The streams and futures the guest returns, which the host reads or - // drops, and its resources, which the host drops. The host's own - // resources are the host's drops too. - Fuzz::consume(res, &mut u); - SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); - proxy::conversion::executor::host_drops(false); - // Lets the guest, and the host's background work, run between calls. - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } - } - } + // The streams and futures the guest returns, which the host reads or drops, and + // its resources, which the host drops. + let after = quote! { Fuzz::consume(res, &mut u); }; + let call = self.start_call(&func, args, before, after); + quote! { { #call } } }) .collect(); let func_len = arms.len(); @@ -169,21 +145,6 @@ impl State { self.generate_start( sig, quote! { - /// Calls an async export, and records that the guest started it, if it blocks. - #[allow(dead_code)] - async fn call_export(id: TaskId, call: impl std::future::Future) -> T { - let mut call = std::pin::pin!(call); - let mut started = false; - std::future::poll_fn(|cx| { - let poll = call.as_mut().poll(cx); - if poll.is_pending() && !started { - started = true; - proxy::recorder::record::record(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); - } - poll - }) - .await - } let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index bf16635..c75b0de 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -381,8 +381,49 @@ impl State { Mode::Replay => quote! { proxy::recorder::replay::finish(); }, _ => quote! {}, }; + // What the export calls of fuzz and replay modes share (see `start_call`), so that they + // stay alike, as replay requires: fuzz mode records a guest line, which replay checks. + let trace_guest = match self.mode { + Mode::Fuzz => quote! { proxy::recorder::record::record(line); }, + Mode::Replay => quote! { proxy::recorder::replay::check(line); }, + _ => quote! {}, + }; + let start_defs = match self.mode { + Mode::Fuzz | Mode::Replay => quote! { + /// A line of the guest, which fuzz mode records, and replay checks. + #[allow(dead_code)] + fn trace_guest(line: &Line) { + #trace_guest + } + /// Calls an async export, and reports that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + trace_guest(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); + } + poll + }) + .await + } + /// Waits until the executor's step `step`, which lets the guest, and the host's + /// background work, run, and takes the next step, of a host line `start` makes, + /// e.g. an export call's arrival. + #[allow(dead_code)] + async fn arrive_after(step: u32) -> u32 { + proxy::conversion::executor::wait_until_step(step).await; + proxy::conversion::executor::step() + } + }, + _ => quote! {}, + }; parse_quote! { #sig { + #start_defs let #executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); #body join_spawned().await; @@ -392,6 +433,49 @@ impl State { } } } + /// An export call of `start`, in fuzz and replay modes: `args` binds the arguments, `before` + /// runs before the call, with `__id` the call's, and `after` gets the result, `res`. The + /// guest's `return` is reported (see `trace_guest`), and the arguments the call borrowed are + /// dropped at the end of their block, which, like those of the result, are the host's drops. + fn start_call( + &self, + func: &ExportFunc, + args: TokenStream, + before: TokenStream, + after: TokenStream, + ) -> TokenStream { + let ExportFunc { + func, + call_params, + is_async, + has_ret, + .. + } = func; + // An async call that blocks gives the host a subtask, which the guest started. + let call = if *is_async { + quote! { call_export(__id, #func(#(#call_params),*)).await } + } else { + quote! { #func(#(#call_params),*) } + }; + let value = if *has_ret { + quote! { Some(wasm_wave::to_string(&res.to_value()).unwrap()) } + } else { + quote! { None } + }; + quote! { + let res = { + #args + #before + let res = #call; + trace_guest(&trace_return(Party::Guest, __id, #value)); + proxy::conversion::executor::host_drops(true); + res + }; + #after + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); + } + } /// The functions of `proxy:conversion/executor`, which the imports component exports, for the /// `start` function of the exports component to run its jobs (see `assets/executor.rs`). fn generate_executor_func(&self, sig: &Signature) -> syn::ImplItemFn { @@ -404,6 +488,7 @@ impl State { ("run", _) => quote! { run_executor().await }, ("stop", _) => quote! { stop_executor() }, ("step", _) => quote! { next_step() }, + ("current_step", _) => quote! { current_step() }, ("wait_until_step", _) => quote! { wait_until_step(step).await }, ("host_drops", Mode::Replay | Mode::Fuzz) => { quote! { MOVING.with(|moving| moving.set(dropping)) } @@ -506,7 +591,14 @@ fn job_defs(mode: &Mode, has_executor: Option) -> Vec { let Event::Return(ret) = line.event else { panic!("the trace answers the call with {:?}", line.event); }; - wait_until_step(line.at.expect("a STARTED call returns in a step")).await; + // A STARTED call blocks, so it returns in a later step than the one it was + // called in; if replay is there already, it is ahead of the recording. + let at = line.at.expect("a STARTED call returns in a step"); + let now = current_step(); + assert!(now < at, "replay is in step {now}, past step {at} of the return of {id:?}"); + wait_until_step(at).await; + let now = current_step(); + assert_eq!(now, at, "replay returned {id:?} in step {now}, not in step {at}"); ret.value } }; diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 888ad97..d6be332 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -137,84 +137,38 @@ impl State { let arms = self.export_funcs().into_iter().map(|func| { let ExportFunc { display_name, - func, arg_names, arg_tys, - call_params, - has_ret, ret_owns, - is_async, .. - } = func; - // An async call that blocks gives the host a subtask, which the guest started. - let call = if is_async { - quote! { call_export(__id, #func(#(#call_params),*)).await } - } else { - quote! { #func(#(#call_params),*) } - }; + } = &func; let arg_idx = (0..arg_names.len()).map(|idx| quote! { args[#idx] }); - // The export call's id is the trace's, as the trace makes the call. - let assert_ret = if has_ret { - quote! { - let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::replay::check(&trace_return(Party::Guest, __id, Some(wave_res))); - } - } else { - quote! { - assert!(res == ()); - proxy::recorder::replay::check(&trace_return(Party::Guest, __id, None)); - } + let args = quote! { + #( + let arg_value: Value = wasm_wave::from_str(&<#arg_tys as ValueTyped>::value_type(), &#arg_idx).unwrap(); + let #arg_names: #arg_tys = FromWave::from_wave(&arg_value); + )* }; // The host keeps the resources it gets, until the trace drops them. - let keep_ret = if ret_owns { + let after = if *ret_owns { quote! { std::mem::forget(res); } } else { - quote! {} + quote! { let _ = res; } }; - // The arguments the call borrowed are dropped at the end of the block, which are - // the host's drops, as in fuzz mode, not the guest's. - quote! { - #display_name => { - let res = { - #( - let arg_value: Value = wasm_wave::from_str(&<#arg_tys as ValueTyped>::value_type(), &#arg_idx).unwrap(); - let #arg_names: #arg_tys = FromWave::from_wave(&arg_value); - )* - let res = #call; - proxy::conversion::executor::host_drops(true); - res - }; - proxy::conversion::executor::host_drops(false); - #assert_ret - #keep_ret - } - } + // The call's id is the trace's, as the trace makes the call, in its step (see + // below). + let call = self.start_call(&func, args, quote! {}, after); + quote! { #display_name => { #call } } }); let drop_arm = self.replay_export_drop(); self.generate_start( sig, quote! { - /// Calls an async export, and checks that the guest started it, if it blocks. - #[allow(dead_code)] - async fn call_export(id: TaskId, call: impl std::future::Future) -> T { - let mut call = std::pin::pin!(call); - let mut started = false; - std::future::poll_fn(|cx| { - let poll = call.as_mut().poll(cx); - if poll.is_pending() && !started { - started = true; - proxy::recorder::replay::check(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); - } - poll - }) - .await - } while let Some(line) = proxy::recorder::replay::next_host_line() { // A line in step `at` follows the step before it, and takes its own step, as // an export call's arrival does. if let Some(at) = line.at { - proxy::conversion::executor::wait_until_step(at - 1).await; - let step = proxy::conversion::executor::step(); + let step = arrive_after(at - 1).await; assert_eq!(step, at, "replay came to {:?} in step {step}", line.event); } match line.event { @@ -227,12 +181,6 @@ impl State { #drop_arm event => unreachable!("`start` doesn't perform {event:?}"), } - // clean up borrowed resources from input args - proxy::conversion::executor::host_drops(true); - SCOPED_ALLOC.with(|alloc| { - alloc.borrow_mut().clear(); - }); - proxy::conversion::executor::host_drops(false); } }, ) diff --git a/src/instrument.rs b/src/instrument.rs index 7280bed..863ba2f 100644 --- a/src/instrument.rs +++ b/src/instrument.rs @@ -37,8 +37,8 @@ const DEBUG_WASM_ONE_BYTE_PAGE: &[u8] = include_bytes!("../assets/debug.one_byte const RECORDER_WASM_ONE_BYTE_PAGE: &[u8] = include_bytes!("../assets/recorder.one_byte_page.wasm"); pub fn run(args: InstrumentArgs) -> Result<()> { - if args.use_host_recorder && !matches!(args.mode, Mode::Record | Mode::Replay) { - anyhow::bail!("--use-host-recorder only works in record or replay mode"); + if args.use_host_recorder && !matches!(args.mode, Mode::Record | Mode::Replay | Mode::Fuzz) { + anyhow::bail!("--use-host-recorder only works in record, replay or fuzz mode"); } // 1. Create a tmp directory and initialize a new Rust project in it. let tmp_dir = init_rust_project()?; diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index a16f869..0e5ef17 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -54,7 +54,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::guest::api::run(&p, &c); + let res = test::guest::api::run(&p, &c); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -81,7 +89,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::guest::api::classify(&s); + let res = test::guest::api::classify(&s); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -108,7 +124,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::guest::api::begin(c); + let res = test::guest::api::begin(c); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -140,7 +164,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.join(", ") ), ); - let _ = test::guest::api::Session::new(&name); + let res = test::guest::api::Session::new(&name); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -175,7 +207,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.join(", ") ), ); - let _ = test::host::types::make_point(x, y); + let res = test::host::types::make_point(x, y); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -208,7 +248,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::host::types::check(p, c); + let res = test::host::types::check(p, c); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -238,7 +286,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::host::types::open(&name); + let res = test::host::types::open(&name); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -268,7 +324,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.join(", ") ), ); - let _ = test::host::types::Counter::new(start); + let res = test::host::types::Counter::new(start); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -306,7 +370,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params.join(", ") ), ); - let _ = test::host::types::Counter::merge(&a, b); + let res = test::host::types::Counter::merge(&a, b); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -647,18 +719,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a /// local trait, so that it can be implemented for wit-bindgen's streams, futures and /// error-contexts, as `Fuzz` is for `Arbitrary`. @@ -731,6 +791,18 @@ impl FromWave for wit_bindgen::rt::async_support::ErrorContext { Self::new("") } } +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} impl Dialog for test::guest::api::Session { fn read_value(_dep: u32) -> Self { { diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index e8298f9..e68ae01 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -29,6 +29,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + fn current_step() -> u32 { + current_step() + } async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } @@ -455,18 +458,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a /// local trait, so that it can be implemented for wit-bindgen's streams, futures and /// error-contexts, as `Fuzz` is for `Arbitrary`. @@ -539,6 +530,18 @@ impl FromWave for wit_bindgen::rt::async_support::ErrorContext { Self::new("") } } +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} impl Dialog for exports::test::host::types::Point { fn read_value(dep: u32) -> Self { proxy::util::dialog::print( @@ -795,23 +798,15 @@ async fn wait_for_runs(runs: u32) -> u32 { }) .await } -/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is -/// already there, e.g. replay's for a host line recorded in that step. -/// -/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. #[allow(dead_code)] async fn wait_until_step(step: u32) { - if current_step() == step { + if current_step() >= step { return; } - wait_for_run(move || { - let now = current_step(); - assert!( - now <= step, "replay reached step {now} before the run in step {step}" - ); - now == step - }) - .await; + wait_for_run(move || current_step() >= step).await; } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index a498d22..fdac5ab 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -4,8 +4,12 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); - /// Calls an async export, and records that the guest started it, if it blocks. + /// A line of the guest, which fuzz mode records, and replay checks. + #[allow(dead_code)] + fn trace_guest(line: &Line) { + proxy::recorder::record::record(line); + } + /// Calls an async export, and reports that the guest started it, if it blocks. #[allow(dead_code)] async fn call_export( id: TaskId, @@ -17,7 +21,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let poll = call.as_mut().poll(cx); if poll.is_pending() && !started { started = true; - proxy::recorder::record::record( + trace_guest( &Line { by: Party::Guest, at: None, @@ -29,6 +33,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }) .await } + /// Waits until the executor's step `step`, which lets the guest, and the host's + /// background work, run, and takes the next step, of a host line `start` makes, + /// e.g. an export call's arrival. + #[allow(dead_code)] + async fn arrive_after(step: u32) -> u32 { + proxy::conversion::executor::wait_until_step(step).await; + proxy::conversion::executor::step() + } + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -44,7 +57,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -58,7 +75,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::guest::api::run(&p, &c); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -71,9 +88,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 2usize => { let res = { @@ -82,7 +96,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -96,7 +114,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::guest::api::classify(&s); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -109,9 +127,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 3usize => { let res = { @@ -120,7 +135,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -134,7 +153,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::guest::api::begin(c); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -147,9 +166,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 4usize => { let res = { @@ -160,7 +176,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&name)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -174,7 +194,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::guest::api::Session::new(&name); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -187,9 +207,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 5usize => { let res = { @@ -201,7 +218,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -215,7 +236,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::host::types::make_point(x, y); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -228,9 +249,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 6usize => { let res = { @@ -242,7 +260,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -256,7 +278,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::host::types::check(p, c); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -269,9 +291,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 7usize => { let res = { @@ -282,7 +301,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&name)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -296,7 +319,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::host::types::open(&name); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -309,9 +332,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 8usize => { let res = { @@ -322,7 +342,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&start)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -336,7 +360,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::host::types::Counter::new(start); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -349,9 +373,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 9usize => { let res = { @@ -363,7 +384,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -377,7 +402,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::host::types::Counter::merge(&a, b); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -390,9 +415,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } _ => unreachable!(), } @@ -601,18 +623,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} impl Fuzz for test::guest::api::Session { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok({ diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 289b40d..df359f0 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -28,6 +28,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + fn current_step() -> u32 { + current_step() + } async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } @@ -518,18 +521,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} impl Fuzz for exports::test::host::types::Point { fn fuzz(u: &mut Unstructured<'_>) -> Result { Ok(exports::test::host::types::Point { @@ -823,23 +814,15 @@ async fn wait_for_runs(runs: u32) -> u32 { }) .await } -/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is -/// already there, e.g. replay's for a host line recorded in that step. -/// -/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. #[allow(dead_code)] async fn wait_until_step(step: u32) { - if current_step() == step { + if current_step() >= step { return; } - wait_for_run(move || { - let now = current_step(); - assert!( - now <= step, "replay reached step {now} before the run in step {step}" - ); - now == step - }) - .await; + wait_for_run(move || current_step() >= step).await; } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index 04aca03..83d1bca 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -48,7 +48,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::p3::api::handle(url).await; + let res = test::p3::api::handle(url).await; + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -76,7 +84,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - Dialog::consume(test::p3::api::serve(port).await, 0); + let res = test::p3::api::serve(port).await; + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + Dialog::consume(res, 0); join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -107,7 +123,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - Dialog::consume(test::p3::api::echo(s), 0); + let res = test::p3::api::echo(s); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + Dialog::consume(res, 0); join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -137,7 +161,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::p3::host::fetch(url).await; + let res = test::p3::host::fetch(url).await; + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -165,7 +197,8 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::p3::host::sleep(ms).await; + let res = test::p3::host::sleep(ms).await; + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -198,7 +231,8 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::p3::host::report(err); + let res = test::p3::host::report(err); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -231,7 +265,8 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::p3::host::finish(done); + let res = test::p3::host::finish(done); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -266,7 +301,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - let _ = test::p3::host::Body::new(contents); + let res = test::p3::host::Body::new(contents); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + let _ = res; join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -296,7 +339,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .join(", ") ), ); - Dialog::consume(test::p3::host::Body::consume(this), 0); + let res = test::p3::host::Body::consume(this); + proxy::util::dialog::print( + 0, + &format!( + "ret: {}", wasm_wave::to_string(& ToValue::to_value(& res)) + .unwrap() + ), + ); + Dialog::consume(res, 0); join_spawned().await; proxy::conversion::executor::stop(); __executor.await; @@ -466,18 +517,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a /// local trait, so that it can be implemented for wit-bindgen's streams, futures and /// error-contexts, as `Fuzz` is for `Arbitrary`. @@ -550,6 +589,18 @@ impl FromWave for wit_bindgen::rt::async_support::ErrorContext { Self::new("") } } +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} impl Dialog for test::p3::host::ErrorCode { fn read_value(dep: u32) -> Self { let idx = proxy::util::dialog::read_select( diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index ecb5e42..4c77568 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -29,6 +29,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + fn current_step() -> u32 { + current_step() + } async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } @@ -311,18 +314,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a /// local trait, so that it can be implemented for wit-bindgen's streams, futures and /// error-contexts, as `Fuzz` is for `Arbitrary`. @@ -395,6 +386,18 @@ impl FromWave for wit_bindgen::rt::async_support::ErrorContext { Self::new("") } } +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} impl Dialog for exports::test::p3::host::ErrorCode { fn read_value(dep: u32) -> Self { let idx = proxy::util::dialog::read_select( @@ -711,23 +714,15 @@ async fn wait_for_runs(runs: u32) -> u32 { }) .await } -/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is -/// already there, e.g. replay's for a host line recorded in that step. -/// -/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. #[allow(dead_code)] async fn wait_until_step(step: u32) { - if current_step() == step { + if current_step() >= step { return; } - wait_for_run(move || { - let now = current_step(); - assert!( - now <= step, "replay reached step {now} before the run in step {step}" - ); - now == step - }) - .await; + wait_for_run(move || current_step() >= step).await; } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 5cb0f60..1ed2087 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -4,8 +4,12 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); - /// Calls an async export, and records that the guest started it, if it blocks. + /// A line of the guest, which fuzz mode records, and replay checks. + #[allow(dead_code)] + fn trace_guest(line: &Line) { + proxy::recorder::record::record(line); + } + /// Calls an async export, and reports that the guest started it, if it blocks. #[allow(dead_code)] async fn call_export( id: TaskId, @@ -17,7 +21,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let poll = call.as_mut().poll(cx); if poll.is_pending() && !started { started = true; - proxy::recorder::record::record( + trace_guest( &Line { by: Party::Guest, at: None, @@ -29,6 +33,15 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }) .await } + /// Waits until the executor's step `step`, which lets the guest, and the host's + /// background work, run, and takes the next step, of a host line `start` makes, + /// e.g. an export call's arrival. + #[allow(dead_code)] + async fn arrive_after(step: u32) -> u32 { + proxy::conversion::executor::wait_until_step(step).await; + proxy::conversion::executor::step() + } + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); for _ in 0..10 { @@ -43,7 +56,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&url)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -57,7 +74,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = call_export(__id, test::p3::api::handle(url)).await; - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -70,9 +87,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 2usize => { let res = { @@ -83,7 +97,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&port)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -97,7 +115,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = call_export(__id, test::p3::api::serve(port)).await; - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -110,9 +128,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 3usize => { let res = { @@ -124,7 +139,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { __params .push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -138,7 +157,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::p3::api::echo(s); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -151,9 +170,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 4usize => { let res = { @@ -164,7 +180,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&url)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -178,7 +198,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = call_export(__id, test::p3::host::fetch(url)).await; - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -191,9 +211,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 5usize => { let res = { @@ -204,7 +221,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&ms)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -218,18 +239,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = call_export(__id, test::p3::host::sleep(ms)).await; - proxy::recorder::record::record( - &trace_return(Party::Guest, __id, None), - ); + trace_guest(&trace_return(Party::Guest, __id, None)); proxy::conversion::executor::host_drops(true); res }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 6usize => { let res = { @@ -243,7 +259,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&err)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -257,18 +277,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::p3::host::report(err); - proxy::recorder::record::record( - &trace_return(Party::Guest, __id, None), - ); + trace_guest(&trace_return(Party::Guest, __id, None)); proxy::conversion::executor::host_drops(true); res }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 7usize => { let res = { @@ -281,7 +296,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&done)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -295,18 +314,13 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::p3::host::finish(done); - proxy::recorder::record::record( - &trace_return(Party::Guest, __id, None), - ); + trace_guest(&trace_return(Party::Guest, __id, None)); proxy::conversion::executor::host_drops(true); res }; Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 8usize => { let res = { @@ -319,7 +333,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&contents)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -333,7 +351,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::p3::host::Body::new(contents); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -346,9 +364,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } 9usize => { let res = { @@ -359,7 +374,11 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { wasm_wave::to_string(&ToValue::to_value(&this)).unwrap(), ); let __id = trace_task_id(Table::Exports); - let __at = proxy::conversion::executor::step(); + let __runs = u.int_in_range(0..=3u8).unwrap() as u32; + let __at = arrive_after( + proxy::conversion::executor::current_step() + __runs, + ) + .await; proxy::recorder::record::record( &Line { at: Some(__at), @@ -373,7 +392,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }, ); let res = test::p3::host::Body::consume(this); - proxy::recorder::record::record( + trace_guest( &trace_return( Party::Guest, __id, @@ -386,9 +405,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { Fuzz::consume(res, &mut u); SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - for _ in 0..u.int_in_range(0..=3u8).unwrap() { - wit_bindgen::yield_async().await; - } } _ => unreachable!(), } @@ -510,18 +526,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} impl Fuzz for test::p3::host::ErrorCode { fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(2usize)?; diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 4379409..30b7de2 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -28,6 +28,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + fn current_step() -> u32 { + current_step() + } async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } @@ -429,18 +432,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} impl Fuzz for exports::test::p3::host::ErrorCode { fn fuzz(u: &mut Unstructured<'_>) -> Result { let idx = u.choose_index(2usize)?; @@ -868,23 +859,15 @@ async fn wait_for_runs(runs: u32) -> u32 { }) .await } -/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is -/// already there, e.g. replay's for a host line recorded in that step. -/// -/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. #[allow(dead_code)] async fn wait_until_step(step: u32) { - if current_step() == step { + if current_step() >= step { return; } - wait_for_run(move || { - let now = current_step(); - assert!( - now <= step, "replay reached step {now} before the run in step {step}" - ); - now == step - }) - .await; + wait_for_run(move || current_step() >= step).await; } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index bf850ce..8143783 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -4,8 +4,12 @@ struct Stub; bindings::export!(Stub with_types_in bindings); impl exports::proxy::recorder::start_replay::Guest for Stub { async fn start() -> () { - let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); - /// Calls an async export, and checks that the guest started it, if it blocks. + /// A line of the guest, which fuzz mode records, and replay checks. + #[allow(dead_code)] + fn trace_guest(line: &Line) { + proxy::recorder::replay::check(line); + } + /// Calls an async export, and reports that the guest started it, if it blocks. #[allow(dead_code)] async fn call_export( id: TaskId, @@ -17,7 +21,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let poll = call.as_mut().poll(cx); if poll.is_pending() && !started { started = true; - proxy::recorder::replay::check( + trace_guest( &Line { by: Party::Guest, at: None, @@ -29,10 +33,18 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { }) .await } + /// Waits until the executor's step `step`, which lets the guest, and the host's + /// background work, run, and takes the next step, of a host line `start` makes, + /// e.g. an export call's arrival. + #[allow(dead_code)] + async fn arrive_after(step: u32) -> u32 { + proxy::conversion::executor::wait_until_step(step).await; + proxy::conversion::executor::step() + } + let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); while let Some(line) = proxy::recorder::replay::next_host_line() { if let Some(at) = line.at { - proxy::conversion::executor::wait_until_step(at - 1).await; - let step = proxy::conversion::executor::step(); + let step = arrive_after(at - 1).await; assert_eq!(step, at, "replay came to {:?} in step {step}", line.event); } match line.event { @@ -57,15 +69,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &arg_value, ); let res = test::guest::api::run(&p, &c); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; + let _ = res; + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); } "test:guest/api@0.1.0.classify" => { let res = { @@ -78,15 +94,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &arg_value, ); let res = test::guest::api::classify(&s); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; + let _ = res; + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); } "test:guest/api@0.1.0.begin" => { let res = { @@ -99,16 +119,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &arg_value, ); let res = test::guest::api::begin(c); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; - proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); std::mem::forget(res); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); } "new test:guest/api/session@0.1.0" => { let res = { @@ -119,16 +142,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .unwrap(); let name: String = FromWave::from_wave(&arg_value); let res = test::guest::api::Session::new(&name); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; - proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); std::mem::forget(res); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); } "test:host/types@0.1.0.make-point" => { let res = { @@ -145,15 +171,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .unwrap(); let y: i32 = FromWave::from_wave(&arg_value); let res = test::host::types::make_point(x, y); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; + let _ = res; + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); } "test:host/types@0.1.0.check" => { let res = { @@ -174,15 +204,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &arg_value, ); let res = test::host::types::check(p, c); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; + let _ = res; + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); } "test:host/types@0.1.0.open" => { let res = { @@ -193,16 +227,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .unwrap(); let name: String = FromWave::from_wave(&arg_value); let res = test::host::types::open(&name); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; - proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); std::mem::forget(res); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); } "new test:host/types/counter@0.1.0" => { let res = { @@ -213,16 +250,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .unwrap(); let start: u32 = FromWave::from_wave(&arg_value); let res = test::host::types::Counter::new(start); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; - proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); std::mem::forget(res); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); } "test:host/types/counter@0.1.0.merge" => { let res = { @@ -243,16 +283,19 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { &arg_value, ); let res = test::host::types::Counter::merge(&a, b); + trace_guest( + &trace_return( + Party::Guest, + __id, + Some(wasm_wave::to_string(&res.to_value()).unwrap()), + ), + ); proxy::conversion::executor::host_drops(true); res }; - proxy::conversion::executor::host_drops(false); - let wave_res = wasm_wave::to_string(&res.to_value()) - .unwrap(); - proxy::recorder::replay::check( - &trace_return(Party::Guest, __id, Some(wave_res)), - ); std::mem::forget(res); + SCOPED_ALLOC.with(|alloc| alloc.borrow_mut().clear()); + proxy::conversion::executor::host_drops(false); } _ => unreachable!("the trace calls an unknown export {func}"), } @@ -278,12 +321,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } event => unreachable!("`start` doesn't perform {event:?}"), } - proxy::conversion::executor::host_drops(true); - SCOPED_ALLOC - .with(|alloc| { - alloc.borrow_mut().clear(); - }); - proxy::conversion::executor::host_drops(false); } join_spawned().await; proxy::conversion::executor::stop(); @@ -612,18 +649,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a /// local trait, so that it can be implemented for wit-bindgen's streams, futures and /// error-contexts, as `Fuzz` is for `Arbitrary`. @@ -696,6 +721,18 @@ impl FromWave for wit_bindgen::rt::async_support::ErrorContext { Self::new("") } } +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} use bindings::proxy::recorder::types::*; thread_local! { static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index fda96d2..a5f6acb 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -27,6 +27,9 @@ impl exports::proxy::conversion::executor::Guest for Stub { fn step() -> u32 { next_step() } + fn current_step() -> u32 { + current_step() + } async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } @@ -497,18 +500,6 @@ impl ToValue for &MockedResource { Value::make_handle(label.into()) } } -impl FromWave for MockedResource { - fn from_wave(value: &Value) -> Self { - let label = value.unwrap_handle(); - let (name, handle) = label - .rsplit_once('-') - .and_then(|(name, handle_str)| { - handle_str.parse::().ok().map(|h| (name.to_string(), h)) - }) - .expect("invalid handle label"); - MockedResource { handle, name } - } -} /// A value of the trace, as the Rust type of the bindings: wasm-wave's `ToRust`, as a /// local trait, so that it can be implemented for wit-bindgen's streams, futures and /// error-contexts, as `Fuzz` is for `Arbitrary`. @@ -581,6 +572,18 @@ impl FromWave for wit_bindgen::rt::async_support::ErrorContext { Self::new("") } } +impl FromWave for MockedResource { + fn from_wave(value: &Value) -> Self { + let label = value.unwrap_handle(); + let (name, handle) = label + .rsplit_once('-') + .and_then(|(name, handle_str)| { + handle_str.parse::().ok().map(|h| (name.to_string(), h)) + }) + .expect("invalid handle label"); + MockedResource { handle, name } + } +} use bindings::proxy::recorder::types::*; thread_local! { static TASK_ID : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; @@ -661,7 +664,12 @@ async fn trace_answer(id: TaskId, answers: Vec) -> Option { let Event::Return(ret) = line.event else { panic!("the trace answers the call with {:?}", line.event); }; - wait_until_step(line.at.expect("a STARTED call returns in a step")).await; + let at = line.at.expect("a STARTED call returns in a step"); + let now = current_step(); + assert!(now < at, "replay is in step {now}, past step {at} of the return of {id:?}"); + wait_until_step(at).await; + let now = current_step(); + assert_eq!(now, at, "replay returned {id:?} in step {now}, not in step {at}"); ret.value } thread_local! { @@ -706,23 +714,15 @@ async fn wait_for_runs(runs: u32) -> u32 { }) .await } -/// Waits, from any task, until the executor's run in `step`, or returns at once if the count is -/// already there, e.g. replay's for a host line recorded in that step. -/// -/// Panics if the count passed `step` without a run in it: replay diverged from the recording. +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. #[allow(dead_code)] async fn wait_until_step(step: u32) { - if current_step() == step { + if current_step() >= step { return; } - wait_for_run(move || { - let now = current_step(); - assert!( - now <= step, "replay reached step {now} before the run in step {step}" - ); - now == step - }) - .await; + wait_for_run(move || current_step() >= step).await; } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. From e94c15698d27cee440b69f708524614e0c725f7a Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Thu, 5 Nov 2026 18:50:13 -0800 Subject: [PATCH 16/25] Replay streams and futures, and record unique labels via alpha conversion - Replay of streams, futures and error-contexts: a local `Replay` trait (src/traits/replay.rs) replays the host's values from the trace's WAVE text and consumes the guest's. The host's ends run as executor jobs that perform each trace line in its step and check what the guest wrote. Host-created streams and futures are paired with the trace's label through the new `pair` recorder function. - host_stream/host_future, shared by fuzz, dialog and replay, label a stream or future by the writer the host keeps. Only fuzz and dialog print the reader under that label, through wasm-wave's `set_handle_label`. - The host's drops of mocks, e.g. of the borrowed arguments of an export call, are marked through a new `host-drops` executor export, so they are recorded as the host's drops, without a step. - Generated components send their panics to the recorder's `fail`, as they have no stderr of their own. - Executor: `arrive-after` waits for a step and takes the next one in a single call. The executor is held in between, so that no run, e.g. for a guest's drop, can fall between the wait and the arrival. Replay would otherwise wait for that run by waking itself, which changes the order in which wasmtime delivers the guest's events. - `start` calls only the main component's exports, not the host's own functions. Those would put both ends of a stream in the generated components, where the imports executor can't take them over, as wasmtime traps intra-component copies of non-numeric payloads. - Labels: the recorder names each binding of a handle number apart as it records (trace::Labels), e.g. #stream-7, then #stream-7-2 after its end. This only looks at earlier lines, so a trace cut short by a trap keeps the same names. Replay's renaming drops generations and the `handed` map. A stream replay makes is paired with the binding after the line that hands out its reader. Replay rejects a trace that reuses a label after its end. - make test-fuzz-replay fuzzes and replays the p3 fixture as well as the p2 one, through a shared run-fuzz-replay target. - docs/p3-record-replay.md: phase 3 done, with label bindings, arrive-after, and outdated and historical text removed. --- Makefile | 21 +- assets/executor.rs | 91 ++- assets/executor.wit | 12 +- assets/recorder.one_byte_page.wasm | Bin 336823 -> 369553 bytes assets/recorder.wasm | Bin 336827 -> 369557 bytes assets/recorder.wit | 10 +- components/recorder/src/lib.rs | 19 +- crates/trace/src/lib.rs | 143 +++-- crates/trace/src/renaming.rs | 513 +++++++++++----- docs/p3-record-replay.md | 110 ++-- src/codegen/fuzz.rs | 36 +- src/codegen/mod.rs | 177 ++++-- src/codegen/replay.rs | 75 +-- src/codegen/wit.rs | 23 +- src/instrument.rs | 29 +- src/run.rs | 29 +- src/traits/dialog.rs | 4 +- src/traits/fuzz.rs | 39 +- src/traits/mod.rs | 27 +- src/traits/replay.rs | 466 ++++++++++++++ src/traits/wave.rs | 231 +------ src/wit_util.rs | 7 +- tests/snapshots/dialog/record_exports/lib.rs | 417 ------------- tests/snapshots/dialog/record_imports/lib.rs | 256 +++----- tests/snapshots/fuzz/record_exports/lib.rs | 279 ++------- tests/snapshots/fuzz/record_imports/lib.rs | 108 +++- .../snapshots/p3-dialog/record_exports/lib.rs | 372 +---------- .../snapshots/p3-dialog/record_imports/lib.rs | 248 +++----- tests/snapshots/p3-fuzz/record_exports/lib.rs | 359 +++-------- tests/snapshots/p3-fuzz/record_imports/lib.rs | 176 ++++-- tests/snapshots/replay/record_exports/lib.rs | 576 +++++++----------- tests/snapshots/replay/record_imports/lib.rs | 499 +++++++++------ 32 files changed, 2443 insertions(+), 2909 deletions(-) create mode 100644 src/traits/replay.rs diff --git a/Makefile b/Makefile index eb90ee7..e9df19e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-fuzz-replay test-fuzz test-dialog run-fuzz run-record run-dialog run-viceroy +.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-fuzz-replay test-fuzz test-dialog run-fuzz run-fuzz-replay run-record run-dialog run-viceroy # `make test ONE_BYTE_PAGE=1` tests with 1-byte-page memories ifeq ($(ONE_BYTE_PAGE),1) @@ -73,16 +73,21 @@ test-fixture: $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm target/release/proxy-component run composed.wasm --invoke 'start()' --trace $(FIXTURE_OUT)/trace.out -# Fuzzes the main component of tests/fixtures/proxy-run/guest, and replays each trace it writes +# Fuzzes the main components of tests/fixtures/proxy-run/guest and of the p3 fixture, and +# replays each trace it writes. test-fuzz-replay: cargo build --manifest-path $(FIXTURE)/guest/Cargo.toml --target wasm32-wasip2 --release --target-dir $(FIXTURE_OUT) - $(INSTRUMENT) -m fuzz $(FIXTURE_WASM)/proxy_guest.wasm - cp composed.wasm $(FIXTURE_OUT)/fuzz.wasm - $(INSTRUMENT) -m replay $(FIXTURE_WASM)/proxy_guest.wasm - cp composed.wasm $(FIXTURE_OUT)/replay.wasm + $(MAKE) run-fuzz-replay WASM=$(FIXTURE_WASM)/proxy_guest.wasm OUT=$(FIXTURE_OUT) + cargo build --manifest-path $(P3_GUEST)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_GUEST_OUT) + $(MAKE) run-fuzz-replay WASM=$(P3_GUEST_WASM) OUT=$(P3_GUEST_OUT) WASMTIME_FLAGS="$(WASMTIME_FLAGS) -W component-model-error-context=y" +run-fuzz-replay: + $(INSTRUMENT) -m fuzz $(WASM) + cp composed.wasm $(OUT)/fuzz.wasm + $(INSTRUMENT) -m replay $(WASM) + cp composed.wasm $(OUT)/replay.wasm for i in 1 2 3 4 5 6 7 8 9 10; do \ - $(WASMTIME) --invoke 'start()' $(FIXTURE_OUT)/fuzz.wasm > $(FIXTURE_OUT)/fuzz-trace.out || exit 1; \ - $(WASMTIME) --invoke 'start()' $(FIXTURE_OUT)/replay.wasm < $(FIXTURE_OUT)/fuzz-trace.out > /dev/null || exit 1; \ + $(WASMTIME) --invoke 'start()' $(OUT)/fuzz.wasm > $(OUT)/fuzz-trace.out || exit 1; \ + $(WASMTIME) --invoke 'start()' $(OUT)/replay.wasm < $(OUT)/fuzz-trace.out > /dev/null || exit 1; \ done test-dialog: diff --git a/assets/executor.rs b/assets/executor.rs index e3dde3d..8a1de8f 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -61,6 +61,10 @@ thread_local! { static STEP: std::cell::Cell = const { std::cell::Cell::new(0) }; // Set by a job waiting for a later run, so that the executor wakes itself. static WAKE_AGAIN: std::cell::Cell = const { std::cell::Cell::new(false) }; + // Set from the run in which an arrival's wait ends until it takes its step (see + // `arrive_after`), and whether the executor was woken meanwhile. + static HELD: std::cell::Cell = const { std::cell::Cell::new(false) }; + static WOKEN_WHILE_HELD: std::cell::Cell = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. #[allow(dead_code)] @@ -78,13 +82,16 @@ fn current_step() -> u32 { #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { let mut left = runs; - wait_for_run(move || { - if left == 0 { - return true; - } - left -= 1; - false - }) + wait_for_run( + move || { + if left == 0 { + return true; + } + left -= 1; + false + }, + false, + ) .await } /// Waits, from any task, until a run of the executor in `step` or later, or returns at once if @@ -95,11 +102,46 @@ async fn wait_until_step(step: u32) { if current_step() >= step { return; } - wait_for_run(move || current_step() >= step).await; + wait_for_run(move || current_step() >= step, false).await; +} +/// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line +/// `start` makes, e.g. an export call's arrival, which it returns. The executor doesn't run in +/// between, e.g. for a guest's event, so that the step follows the run it waited for, which +/// replay waits for from the step alone. +#[allow(dead_code)] +async fn arrive_after(step: u32) -> u32 { + if current_step() < step { + wait_for_run(move || current_step() >= step, true).await; + } + HELD.set(false); + let step = next_step(); + if WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + step +} +/// Waits, in a job of the executor, until its run in `step`, waking the executor itself until +/// then, e.g. replay's job for a copy recorded in that step. +/// +/// Panics if the count passed `step`: replay diverged from the recording. +#[allow(dead_code)] +async fn reach_step(step: u32) { + std::future::poll_fn(|_| { + let now = current_step(); + assert!(now <= step, "replay reached step {now} before the run in step {step}"); + if now == step { + std::task::Poll::Ready(()) + } else { + WAKE_AGAIN.set(true); + std::task::Poll::Pending + } + }) + .await } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job -/// asks in each run, waking the executor itself in between, as no host event would. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { +/// asks in each run, waking the executor itself in between, as no host event would. With `hold`, +/// the executor doesn't run again until `arrive_after` releases it. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u32 { #[derive(Default)] struct Done { step: Option, @@ -109,6 +151,9 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { let job_shared = shared.clone(); spawn(std::future::poll_fn(move |_| { if done() { + if hold { + HELD.set(true); + } let mut shared = job_shared.borrow_mut(); shared.step = Some(current_step()); if let Some(waker) = shared.waker.take() { @@ -136,17 +181,27 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { fn spawn(job: impl std::future::Future + 'static) { EXECUTOR.with_borrow_mut(|e| { e.queue.push(Box::pin(job)); - // Wakes `run_executor` from another task, with wit-bindgen's `inter-task-wakeup`. - if let Some(waker) = e.waker.take() { - waker.wake(); - } }); + wake_executor(); +} +/// Wakes `run_executor` from another task, with wit-bindgen's `inter-task-wakeup`. +fn wake_executor() { + let waker = EXECUTOR.with_borrow_mut(|e| e.waker.take()); + if let Some(waker) = waker { + waker.wake(); + } } /// Polls the queued jobs until `stop_executor` is called and they are all done. #[allow(dead_code)] async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + // Held, it isn't a run: the jobs are polled at the next, as every run polls them all. + if HELD.get() { + WOKEN_WHILE_HELD.set(true); + EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); + return std::task::Poll::Pending; + } next_step(); WAKE_AGAIN.set(false); running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); @@ -176,10 +231,6 @@ async fn run_executor() { } #[allow(dead_code)] fn stop_executor() { - EXECUTOR.with_borrow_mut(|e| { - e.stopped = true; - if let Some(waker) = e.waker.take() { - waker.wake(); - } - }); + EXECUTOR.with_borrow_mut(|e| e.stopped = true); + wake_executor(); } diff --git a/assets/executor.wit b/assets/executor.wit index 614b1e8..34b2204 100644 --- a/assets/executor.wit +++ b/assets/executor.wit @@ -8,15 +8,17 @@ package proxy:conversion; interface executor { run: async func(); stop: func(); - // Counts a step that isn't a run of the executor, e.g. the arrival of an export call, which the - // exports component makes, and returns it (see `docs/p3-record-replay.md`, Steps). + // Counts a step that isn't a run of the executor, e.g. the host's drop of a guest's resource, + // which the exports component makes, and returns it (see `docs/p3-record-replay.md`, Steps). step: func() -> u32; // The last step. current-step: func() -> u32; - // Waits until the executor's run in `step`, or returns at once if the count is already there: - // `start` waits for the step before an export call's, which fuzz mode chooses, and replay's - // trace has. + // Waits until the executor's run in `step`, or returns at once if the count is already there. wait-until-step: async func(step: u32); + // Waits as `wait-until-step` does, and counts the next step, as `step` does, without a run of + // the executor in between: `start` waits for the step before an export call's, which fuzz mode + // chooses, and replay's trace has, and the call arrives in the next. + arrive-after: async func(step: u32) -> u32; // Whether the exports component is dropping mocks of the imports component itself, e.g. those // it made for the borrowed arguments of an export call: the host's drops, not the guest's. host-drops: func(dropping: bool); diff --git a/assets/recorder.one_byte_page.wasm b/assets/recorder.one_byte_page.wasm index 000267f05859f64cf02b140c5b4c6c1d78ebd66c..334dba753b84cab250b0a377654d90e76e5e216b 100644 GIT binary patch delta 116225 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 NPKz54^s`#c{|21A3+Vs= delta 93179 zcmeEv3!GHNmHw@|w_nrUGd(Z^12e$wX3zmaP?14ITxKzwcDt+uid3HSY5# zkG_wpTXpKxIj2sYI(6#OhevFBxMjoUbW`PL!=2+!KUq1BqkgW&R-R0s)ZmrzN0k>O z%axC3M&$5#cG+2w;M+TvWWMi}Z}ZlBIgL+p3LoYha}7-@)ueI-$I)7+v~C`T_srlF z@PdB|g@RVv%{fYW%%v3mH#H%zs{b>*#Yv%vly+QvZa$VWqg$;jFE*xTIxS7rKPU#Z zd5M&E6JAPtP7CTysT3L|e_C6VO1a8YO(QD1Hu;CQ08*tg8gD$MGsEzwxkZgkp*{TL z$v^(&95gD&!X1=Jh@4alKz4O5)!;hXRk>7C)Hwn|H3zw)R;s-kO0rX?MlsZ*a@HdfE>1|KUb&{*}E@c(;_Vzj1vJ=RU;+^Kt)(6VweFLAe z{AuLRs&8+WoFn*f`zDj;_OF|(Wb0(g1pajKXY+j~ZL`hu##82b4<=6M&z}65!JnD@ zS@9iHL-}5Nv%yxmFH`Tf54|?=wI4qq@O)E#AC%u^HtivKdRTrRz2B6&?TPy91=8d< zzG&WFC{GuwZ0bC>A)7q;lP8~Y+Gn&AuI}+ByN*9SyuQcVDSTp)*QlLhxb&RFc-JWv zD^CvV2p3!c#E!3Q9oDHjL$x9?#%7zqhskQEaN3H*o@J-#oqqgji=5}(+$WAZ{-k41 z`{Z$p7oYM8r{ZQV*UAZ0_A)i@gt22f9@U2)f0bvSi^@93~`jPsL`m_2E^``3c&h@_L zU8pb8_jp(6@|F5hy;6Ts->En1+x1iW9{r%c%lni5mVQoG+<(`<(l_dt^a}R^_s9D4 z?ibvj=nLJ8+@I---AmlB=yTn3+)LeU`W5}M{-yq^{)IlzUGA=Q-_U>1uj@bR%iN#q z8}wWH&-zXMSKaIWML(mLy8oeH(97K4==0rQm-TD?dD#mr%O9|r|wP34azgLQ`U2Qw@V)tln=@~z7CujdZDl9vYbMVlGcK;D_h+Y@{@P=z&2%hmyDK1}xn&pK~5I8IiIs>^|9pm0_AW92_uckk&{ zJq86$Q8_Q)%;p!qx(sFWdtXm6Vz3eaMWbArt;vZ590Ummq+4v(&%pdu3Xn%`P}FN?k*nPf!}S1cC?%J z`A(PaD5(R0E!_FOV#}G1TyuUoYVf>HZQl9q$IOh%3UYyG5tju)G)YDW`1q+Hvqhdg zex1h42bNW~bew=Hri>Yb-$Tbt$8YZ#R{EteV~SCs0ITF3keAOod@t&noL5;rhE3ci zUo?y@;eN{48KdR9oXYx%z&)enpjZ8*?+~!c=f;jlrt8NZh~JmTvW@N+lz<*y=1QWerUfK7UTQ+Yj}a%IZyt z%IEyOcV9;w1zs_S-u4PN@u}2!KtAm^6q~Ysmb}p7XZ^+*x`0d544tc7Ibqk<962Q$ zm`cQp_VO-w&VVH1KY!)4aDz zSB~82_xhD{D+lj&bY&6IxdMBdKxa?EN3z<<@^lYg)3-s z30gZI?A_}WT>vn<$F%rGzCjKbqa^Nn*3b3NljQZwy4`t}N$wSv!s{*Iyk{vR!12Jq zY~gs`LFPnM1)zw^t61DFxZmnn7S6st%FUG3*@>tt`yk@*r}upf`6;dt<3HkRUQZ+Dux)z25mis7YK=-!@mSI9jF8Ic&?K=LyA-Rswz)~ zhSn%QJD2E!KwXXdLfMMcTksHUP^|YB`CY9dnTn3uphT#~dAjtCxbMhImZ6BClAbiaMfw;3vsTX^7EE4TxZsZcCiSRcu>^7bRG|FE z!p)U?cG*c?Q2FsLr&kjbY>l5s;-@B`2$p|*auIItb-xyGAF9>_@xZp%;-vDKX`eni8rLKNJ(->8ZcuS=){c{g>~=Iz zM&oW}zuhKezC@Pt-NGZ4lXvUDsQLNbc5ad`i$>{ZAF1^1)>`h1hHu}^{t=U&i1Vr2 zI7WyYDlRGrbCfCUrh*}L93?sq7+J+C4)l7eI8MZo3w$S1amtG^YEj2=M8|Q07h@gA zsp>d33$+#utC5c5kf9_g=0&6~;E?7p-w9@*CnBOo8>r29qT-ZA$Eoru*tDdOUig#X zPq7tFpn9OTp{gC~I>=O~>!_-(Bhacy*HKp2nH(uQFSO`pNM5U1ZWt8%@lc8x(>uoh zDpl=(mAH?3;A-3lkb?NVT7Fl?5)nPH+DJsG047hDz8v|>U3yg{5;0-ox0IuirU~4y zRB<=p15vn!>W7(CRg+~6(igw)-xqg$ZThha154|S2_x*l!cWb>=QjnQ?fEly9`)=a zR78D@usI5g``%jOwJPKf28!g#OzhQiIPt&}(*pRD;{R`)oF#*cOXQ&lu# zKu7)rWZp~N6piy{b`9XL^2`p_bIr`@hZtZ2HOuKBbNHDLa)|RYpw)&df&ga&C7ZnR zGZvTzXh=1{wC~kXw#7l+=*Yz_izI;2pM9am)etE&4N^hbvFa_|Hh>%>YP6jYtKPs! z%tKQ1jGh2hJGll*w}u+&mZ-VUME6l~k5pdUYtM<%$T4_BRZT+=nKgP~5ogTmjt7YP zA}SBeYA3q;W_QJvC}LB|5QnakuH{6arjq7;hL2CN88NuhtyW-HLI335?|r?=w^8p$ zX1~|oZ-iK5{`_Qf1pOXxs)-G`i2G2WP3Ra5n^52%-A`6#WQEc1MC zNcG5c?T*x=dSNDwGi*>I6FQNg!F?{eukysY1NSto*f(xPji4g_feBD=8!(Z>^e`t9 zWsrjQIEZs`phMkUSu#(=Rr%nmxT;ZW)3&c*vZ)nUzdopzebWO$?VR^F*6_l-9n{cz z@ZVU&)`NFY!>)(4RmL77B52n`L15*;O!Z6>h5h`MIa+Ufl8A^I!4on=O*%jtgI! z?v;OK)q2Oz1QF#vyN>iwIM0m2K{{CF7OMFghRA1Vt&|F0m8pENC&sK5rlddFO47lW z+LBa|JuITox+>$MRslAFr|>SAO`5k(Dq1e5bl*R-XF#-*;^> zr^Uz|3=Hq|D*VN7~oLE#faH9<{MYr@GB)JIE3m_W)@&1q00 zTVmVV)jHfwsd?o#WD%MwSH~i^N>MH%p(s1d{szp5$Ub(Z{T|dzqetTy%`VeU>Kh;4OCgP_RZh@s3R@yE- z+33l*n+8DI7K8c>W-u<`WiC!eD>yW4+ObbQup>xl$!TP;yt^lQT-F!_+Vn%jHB>n5vZ`n9Fugehv@{SEu6Vp@hkV;M|A^7(iX2|0|H zOm^nMk_)kzE%f_n(@j%=q>-=GBjQZfdlu?_Yge`BsD6nnKq+1NLWSfHPK7Q2#4eet zMzlV|GK!g8nteu$ssj4>>Zxk6{{7OhVVXJ}#)_~!O-&fruf#M9%oU^neD#p&q3=&q zdA9lGY3f#WUI^O0l66hpU5!8|9=N+2zh_j=Yo`A&VM9Sz12PMWkWcFj{TdR2HOQ1K zpoD8p318b?9jiGu8_&dw{-y(6`OdTlfhkcJhf(UPL9B3ZjYGHjiX@(lp|2tXZILtKa zOUm{^Q@478fWFwRT7mS}yVd$O(^Lm-nl4;ZV1+IC@Sd4!(tuRt>(U-l|1wjJACT%Z zl^i#7FE#$){w3Ga1tge}2I$V|?4oMVR6&V3?Z&;-m#2JgRlGW`53HEx8F3Q>jvGq8EcaFs0+Jmvi&fCl|^-eV@Lw)6Z~P`tw-wC@SGFWsZ-9AI)U#5@!g3F@SU;v zj=pQ-cWey4J271TNwpBr{Q8q>D(f3}qLA*MC#p$&JkC7+%ZX~waPAB0Q2qQl;UzDq zom<{8BlcgQ`~+{9(fOS4+b^hzGxt=A3y>Hr9oPfPVrcgxlNrXaMe3q6IelkBqD z{~e?8$XKbEW+$rIXOb#a4nKXL8ei5l=B0}+T8|r#>upejiaMvUW>D1Tb*_|N$9q2vSQ*NxVxGg=cYr&fo>*o+4&zFoe0F@gyFZDT{CLMfyW=8OSS{x^%c=KRxWq zxjH=zA9%r;JtYqx000ylj{1gbKP3W8CZqr{S4NVb5UQ?z=ul} zKFA}$N74p-$dt_j)bzo52U&Dj+(#FIl^7PNO>>=Y>R8mCgce};1a1=e?^$&CdaAn2 zWdn7QvlW)qrB`UKfVfOt=>lwwnJ)BR-)oN)8dReMWdH}O3bv5!G*c{40lXnY3J6J4 zHoAD$XY>SDyFKh1;KpN1Q{koeLbUzs{i;KGVR*mVt(+t-1^GS-p^gYnC;aGL+HGO| zp?avOII?Y6zVqUF^4+(Hvi=pLtPcXW)nnIK71!h=G*P3=j^Nw0S6ADs*&Lw0WOEAk zF;x}3RP#}t1Yl0~Rj;~)+J_}n)d|+?1p|{)Weql6a-bph2nEw3n_Jvda=JS7h1%FK zoZvzYw*wEYh>Qr~is8Bg=31`=X9V;j|7)UGQ8Vr`Na-|BHJUa^7L{Q}Z&Er)!}Yo~ zv{jbin+ugw4g-`Jv3d>u(m;(0PJ+4|>DL2ueM%NdeqeEXlYrxZLnOnsi?s#fq_vhn~J*PrlRYBtS?dHBVj0X zu^4!`Hxclu| zdQZ$*H?k&e7u?^wjt&Mdtm7L0o9np7kNX-giWW{s6iJd81lUZkabl2kAz7PFR5WG7 zF+F+@bxU|ek8WxGqnXX#m{#d%v1yxA`Qk0-q%LK%^_+^oeq@0nq|g(n(~2t4#gtpQ ze#`L6$JTdz^g!tT5Q`I?rf#Rn+@-r6D1?0HbUTnsetMR3G`@cpv+zwUGnXI@rJy-3 z3G{&wHIZM=UF-TdK~5B&sNA)F+CkTEl*dBh+x%*VK8oa1Em-cVl?ecGWmrVFRX$Uh za_hk(hxn{=(XFG@J(Zho^&6_SRi3=HG$Mkj`kFvgCf_!p;T{8~E<9HGhugZ!t3+m@ z16CX1^|*o7BcebxbW8vi*nH^w!j5P?NbP8h8DL0*7{ictxg?L!nuaF@uytTQl}n;T zD+4XIx)X)RY>m`MqtqD4C!$0sSEhV+W#Q7wQyYrM*H!qg!IG&^tev11EFpG^xXfNx z`T0h8z&(0wgXs~hXM35ZOR8%rYLyg_lDuEVOD&ArC zt9qiCp$}tU9mcOKhu=A2S5{2(wyFLZavgCy#hDkFVr*t<+`*OG?r3ee&(M@tc;d2e zwHA~4h8$!}ebe51Q@(ra`kSFWi|eTb)g_) znjn%Z_ukc;##`7>D@(cNJiM}Di?`3IhJRAvAyLreOY#tV8`)vjDmLKOitH?;QI2`> zAL9EszoQK5TGE(+W3}f%s^G~f=J5g9%<=P<%`&lUmpfrIxwCARpXHotDzt1SI(HUd z8`*BLUoVY{c`Qwo&1Y!V7m0f<400;0|5`W=CJxEQjyMClbOYHp*T38c4BMnF`{r}- zFxy^B1ZvZg%ICHg$^;+f=EcP+0w}mdMSQfPmXC-XVAeL&)U*UXK(%i(1QSRGkJ_d` z5_7gNV?LLwf;h}zTR7Wb+^HnXP7OliWtDy~7dY(e;HO}LhQeP(n*)>!i})JGD46I0KXO=MG|Qn2peKX? zLsl_S6#@Oq&F3J(VoKG1uCmL&jctgV_!uNoISsR>--Hg0bi!;TPX-hwV?A6FInlwK zhGJ1BIu4iQI0?dBs09~5SHJ~Rq7GcL^jdWM9KeRAFr4wW(CuPO z;ayv|3tfsTpFcw{(9(x9cOY8BIcGu*heleskJuwJ@!;1fBNWdfT$wErt~I%c>p78d zrP1%ed=@;}pS_K6%|pYpDKUe7DoXOAq?j>mijbg4Wg!|!J0p@EiaH~aArj(_hKwSz zg+66c0v{1zxAK{L>De3JJEmMfVs3G~gh96K3C`wRxu!?oD{PQPt5O`s-Ofg%0sSE@ zPMo@O$t|6gZTI~{nd9|~l;?D8y4=Vv{!+NZ zq`a76RyQty#jud$M1g`vi2Jk*1xA4?H%RY6Q}z>9gOWQD?Q&;DI~-&em^!V*qs;*e zYugv?~m6k|D zlhPQl8XF^k%bhev?(DQ6Im?;HD)J7;9iY_o6=*IAVg*OREB@$i7h@Uk=6Aab&E33G zw%c8RFS9pE!y%FZzU{xa2G_SZBA|mU){IuQkmCf!RTBCHMWZxU@!evKb1_r&6DyF& zpadgLu#oKm3xf;x1d6EZ9bZHYB~50QlcRKmOFAnpx0fJ^D4z1-yPSjLvi9&TIh-sM zDQU?_NlVtvY>M=3w;*AL?m^ZY1qo6N_f}9wG+@{FO!1^^w~U$uJ&}|lFGS0LfMG!- zWsC~PDbL_iCSg>#dRB_q$H)s1Sq%cwY|khDdxEnLMMHD->Rb&RHQ>H8Xa}fsv5#W* z>WrI$YmN&!FuKzA&EsnoCO%FCXD|dK+J;1ocp{#u`pCsA38=z>xnizLW&Jo1gcMJ6vgAOZM`lK_dx+*? zi8F5NW2UVzC}rR`ShXf)A~Nn*L7MUhUWX)3n2ilL4#tU9n>iWUc|~?%*qBz(F6+j)~W?{97};Ps9I3NuyZ1YHEIDDX)R7- z(lR4F=N`!#rY&4ij)r(reZ;a@eI}0E`aToKOdONe5KMgKe>W5F{!W>A zUA}Vbqn~T!fC@i({6Y^8X_Tyd@UirSgqd!>iBUJ*17$}t@sHty`0jG5nRY62+Pj#D!2rjl;0Vtprt8W5ig5E~>*2Mrc&Y(S0VXtxL1m zdo@-}SZYyWz`9*wT1&Z?l`f>055lGj`4DbHD4}~3=Vm@3XXu@Xz*A|A=n6EX{Vox z61UoLa{pG-w`wqC)zz4A(CLZFF;9M_d4LHwyzXq+8Mb2P^NmQM5GU_iBksINq2LrO zngP`0BL3z@WAOkZZcO#21&v0Ts4_87Qu*e0N2)~Sx$m}@je>_(1z_g)7N!Au{)>?> ze)jRCPKpbDML%^~*{=8KIsEG<(KzEmhzTI9)iAzs)fgm!MdOU9&ZLjaHRkKMuEYuw zXaZ~GvYe_Wx=`>?3Pg$v>(ESd2aC}(=$SN{B)O5)h(i>AYDpc3l~5@Gk7wH2vJ+Wb zj#^}r+bJ&xiO!cyaWyFNXj3?)6M;mLGKIby-CTL;sa9-&+R$$i?1wR9 zi8)KFx4jRwRspr2mC9bHU&=^N;T>&1_CsQ9}v}dMhVCv-rP| zcz!=X;G9t}aL)Qq6F9>M5eqQaANbzTN@d9pM*bbgr1xBylomOL0t!E1m%SI%&>KsT zZB6M^S)WZKlj8sI((_eYn4PVLg%f70IhD*Q*-aFNz#&*hMNpjG8i%;#dBS-?ldxw z)u2pud$+-Y5x6~)3TL1~H5EY2rco3NR{@Ik8xL+T7U%`oq_F@6EBoFIi)Jy5vgy5XBn7_fFl^HM>8Ou>*2tqfqr&go4k4=%98e<@ERO$sWBpZ`0U z15-A{3g!P~qYQQ7f~QVg3Rt6&LL0G0#%~(oM*-!+?f)3~lO~DC0Xd~B2Q`ld7Dsj(;JRTPF!A}*Qq+bt0m}GsFe3n1# z+|n?+vOU5dQR!XP`j0uk71NqhE6areOzbZhJyFIPJ%!)!b1Gw4G(He2pC~+7?KfL@ z0ldMQ;4<1FLF6E%nWzb8&rzdNaIl47IxoER7}as;mA!qvU@E#-7q&&k`&5Tu3WTS& zabe;Yups#eLjrnmpa5m&I!ERcegnXVlM`UU4v>B4AmUa{>0NW`ON75teTj)G0rVqr zxJ0w65TrM@oeHHPb%4Q@G9c5gFosWjNR1o@7ZV?Qb>dYeIE!8rsD0JF;q!soc^Zf- z+8r?<@(fDMzR#mo9?ktH^XO#5qtgcRXy;%&>c7vU|Hpas8^WW_H9WdCfxvb;JY+9- zTsUu?I$u2*Ze6F2I0zdH=mjiGcGz|V(}eaA!^oQm=ZFIRq&zP1=Wu~-B+b3BX(WrV zz`O<{hMCud3vW?plo3FR*{~~u)`b>an!4p%cul26V#>-jw>7l&lrmVMn4xDPS4J%R z({KluWq1wDLQjhHUTruX+o5tnhlunv)&v|Q)*QBxx3Zq?QSmg`fn~w7d1u$$Y>S0 z1EU3S2c{pigRuk%sxD;!E^IG!Ff-fE_QFIb4G&@QP~F~sIR+OeI|{Q9jsW<<;tbk` zA|D7JI8hx0O38dmO}Ju(n)-ik3nij$o;vO=sx%m^L1J!j=labYy4~PjxhXuiG`vn@ ztK3uJU0Pv=9R5sW58)e2!wz8in!>2(4h<{XU1c5F*9A53JCGV(; z3$|0mnrao#M^$VICnnUv>hAEoq?$AJo+yV?xPk4G9U8RzS{<~{q{FX<{@z~=|BzHO z@$IfDwMTe(Qgsh3hh~OBi`s080=X2ft10KQ9k%v`lpyr=l-kAP?BL3TYH8gT0j`4A zOw%%jN5aF?YB%-yus5xG)J5T|X?3Vt8Fps?=Y`=Z88mxk_;o(541dGR#o?0;swLc~ zLG2gaU&{M0@cohMl1&q`>STpB{-&UIYk0^upm0_Acup17{|mR})FhKSF1)T;O~Czv zyxO;^25V61ZVb2PRZG*i&F5IJ44@c)MPBBC|LcVKQXuGho6Mm2j!aU;+4{ zEdgqMl;O(t`95+OD2{dfFuWhV7~$ee_UkGx4sRA*e5M&hWBRfC6^k(JHN4*9fL8ax zaM03IxGu^Pwl}IV{rjx;xpx?ZqwSRI3?N<=ko@q$JEh92bW@Lb3_`vNP?g8n;_Jd+ z4IkJkwSes1qCNsZUf8O}g^!F-1$9+;$zy6fNc*oNgq(A&YBn+)H&X3el@+e796m!B z!eVU1M1zotlogMUR5PN&b0gHwxIe5-b%%>aU|R5YD}5pA0@rpyze;}=k)cbZTsKIi zn^5Vssv(vVTf^UvQa@>wE!lcb&};U6{%o}R$lu*y`T`s9g2_FFFWdg4uy2b5;#cA< zg=+^(zsb@SiooL8>hPj=LBfOWYDw(D&Fx;*D5L*ozSu?j5hKZ?(U9`I8B%Uu2HtQG z`vagxQ>ZI6BB1#kcS|}L_fPl~u=JkUv&3vcqwa+HCdPL73&86HvqfxqD+izLY4lv| z9`HDS#GAl7lu z7|j)~AnMpm?CRic5wW$o>cP!jw$VQ`d9!tS!}9HJYuGSGHKcw@xstsnocba*6;B(Z zIjwtoI6(SYV%we{>xakZ+OoFooh5D$qFZS z_J#95q1*CrMjwJMmxm9mz-HgfIMtMXfVE~XRFwzXTf*t%5J{m5It3>|hj-2R^TQLy zsXef#det~}k$&#haA#kQ9sQDE1FFjwazoU@61Mn-$NH*m^u2 z2rostx+GldEB^yu{;EX-hDM79&KhK&mKZ^5_GxjrGYSbO4G~T+xw2Dri*5N%t?Usf$Pid0~7SB76S9 zWi{EuvrCv?2sHqViwzhn!@d&Q@QYe7!aoe1@pp9@7mpvR#7WQ9X0(u!5oBPj!AP)D zkhr?GVd0*gs#t#0ut~mfEkCFFA2HAch5&K{Q?4*6*?}pmP0Fx=DYp+^+N545_{NYQ zZLO`Sa!L4D-s*HRT0xxtYE$H#SdararP!GFP?cQ zWLPFh^#o@wDbZ5_)<68q__>26;f=T6dMkxIX2OV^2)#qPF6HxxtVd%fhCytQ|B@K* zxSgJxhq+^jn}3i)!WDcmO6@SIZN+0fE>h=V!7OGVui;bA`Q}^7PrsR^jSUW&CGM?o z|2ar4#x(=;FPyqiDMdRBewc7$7dNUe#D!z~E2~bU>O^JRmv-*JHfxlfo$LIa^;m<) z1~*fqfnH_fm#4cKKVRv&sy!>V+M7xRprEqys<9(rsHGWs7;+YeVK$rd^Gtg1s$zS_ zZ;oxReAzSE!61X>X+Lw>)vaYX=MML?=N7p!2zJv#FzTO6FcNceoWmw)ltUL1+KtVt z6BhTta0|>g`su(vq`RE985m+<$DZgEx}Ac#6Auk}Cte#(L{XD0i^D!npExjq?7c#` zKR>w$11)^(;4ukHrWnjS76;>{n{`i+!2&bi!);;_?6o$c4TgxQF9gdlc-W2I@aDwU z?!Y++q+#~O;Iag+4VmDtpb!_vV&-)lV21ED=_gaU_L?qSh-~{*Is2M)b1pa=%>~## z4DH}Vuaf=B>@sX8=t|=N!+}cZfgOe7(qe%}@fl3jGZ^)ip&6+tJq76Rl^t}jju3;` z0|L0wE}F%GTP_&jL+n~dj91&yjp#Vp?c$-6P;2GLYe$S7=Hnf5=Gpo9F`e(>8JpVk zl|O%Fbiwnprt|YX=H(x*LU?E*4V1hHyp>yYIud06J|NGdrGO74g#INZAV@XcS@WcX zl%a(96+kcL2rsLqpAc3TWL?BB_<7vSz^7OS;RWwKWCL9U3wz3?MoIP1nR%Ovtvr&7 zB28ktCK0Wa#8ab05?}OX!6u#&C1SlHPDHS7`4XG?O|e+WV0dhRlnc%R_=FdjAb_A? z0Lo9>(4mYGBp86J3^AZFy()H1L9#L{$vbgplDoEU7(C_9@76wI)bf+AZpJu( z-wQyMHGT^J2no=gvD9bv)eX1O;8eJf!!SA;BFQGaX=V%61iM=yc&#qs_L}Go41n*J z4W?znKs&ARwZR=m5UOAvP>iVpxALOZG`31aACl2+rnh9Dzl7zn;Sach3wBppcIkSL zFaQO5^Kh}y#n)IsmQaioYzX)P#rIM6HPYZP2R>*a%JCo;T?7I$ICbg$SN&_qfzd^< zRt$208I9y1R52fGV?quj1r1>a4bqSTEEtv)JVOQC^}=;4=)^A(2oeLW$80-kf{tSI zdrFCZ1R(*4u;OKD0&YKsCOk_MG8H3H2NQR=ydq7mw7wQ5C!gL z3Q_RgoR}ssREfb@&kR!ev6Wevi^YJ8kB9Aavz0@cy!6r zVR%YmW`k+8pMa4b7gRD07dns`X#6DLWX(`_g|FxE4h<(8MAPP#!jUC-o;e0gW_Hsh z6v~c9q2O{>(FGg7ss@RBqscA68(}vUMVDYuTGwzRi5|-+RdUyJ$Q+Xek(I)~W5)>R z?h3OX@L(@a8oI5I(?|(LBXf7*TsVM2(t#UX?n{#S@;r7A4IJP?AV>ud;>w-T;GqV? zLl`wkL{R-y@D#3jzMhXoK;fw-@|0heAQiG`&oDk%;yEy%B)Aw0BNhWE^^}^yS^%gt zjAn{qk(mOI03Vw?V=}Z27#0~HdYDC4?2}x;ABJ}L1$sglzJ%u9AdFf<)DeX(URl4{ zaCFhbuD=2UKDc(aeK|2?rl44$mMQ2;6@V7-fk7WQDl`bx!TK8nldgoJ)4}T7ggfqM z85~%t+e0=WR867S%x*%7Ca^4IBjj7~BD$fuUN|n8fiUFA3tl~;1hI%n<02kc%9XDd z8u^GA=M<2gC~-Azr7QuYRzkMTau)k#&tIZ98p+W=N`v z5^6J4STHz2V+C)5VU1s*l_@S)Jdg3+xd5!U58BL8(vKx*N95#Y4r(6~fuRHKH!s2h zuN%A!LD7Oq6kT~zE}G4TLuJPYji?AuQk7?iUo7T2r3Oxth9Mw+DO-8`wbA8 zLiRk#%ks3*8h{dfbPPfY;BpjN0-B<{E%cE#Kp6;x1Nf(5`U38XK#^MmirgAdX~zx~1ISjoU6j)d+Ql-5S9l%BysePy>1uJZ<~PkOKf%NEt^lRQRnhMRP!!IP;)9 zysIGHISEb~=dxznggrmn8_;VgG15j&W=d)p$S_B<9lfninZ*!CsX=i8BMr(z{I}XN zU#!awGLBT3tu~=j{!>S}GlI3i*d!7|49pBf%hEd$c@(>pyk;9E8NrGLGAVMlz+h3M zPxVE9gwVsA{3Z)}y*~A9Qa6Zedv6$?uNuQ?6LdQ4J5~>?y!eA;Gf8FsI6!VPINwkX17@{>Ur&VpA;h9kM4SkDY-s?gJwo&GWE|LV;=poh)FKVU{<%GPD(Y-)5rbFy=&}|zT0-;cG@(&pTnK(-~hw9-@vQ*=8gFb!BFWJAk z2rfDv1c8~twCF6z@VY}(BE0BUB&AF6u!7N0CehH2e3vjpe|-~xl=Wlq0NcawlcqVa zh&fvhOH30T)$NSJ3*2S8ouave*%nJ-w2zbL3pMe+%#_fm)2XtI9UM$JIAI8oYT9fy z;OHq%Gv+F_wT+5vE5@~TncBePQd_()E?2~~ADDtfLF5ltyIB1+!UN5I7CloIGLQO* z*e>GGT1?jJat)G+6HE9&OF%msEvirG9K5A^l9sZNWJjw~IZBoMX=OhiPlnr7(y7|wKmuyMQNw>diEhtWx|UOK?wF8bodTB z8=-}4XG6KSoo%}vo$Ukjf__W|YC0R3CnnGb=n9BIy*IHV6g05Ak#CBVk?sa9p`UF< z=x3=YocAwkYRRZAJ(f;g0p#zPwy6O>h@nHLT7Ba*6R{wiTr$!~~EAcQD_ z93kU)H;^C#_}m%U@UAE#13hK_>5y2Yh?c=bK)B=v#HQFj_meFR_j6UvaKG&_5JX!n zLsBq};~ZcG!B4UjLZS43i$aM*W8K?6c?*t$sY?iIzo@_0)Q0$Cf9qKe7nFe}1SrRb z+aS4wA-0U4j=>L#!65=9gelH6@E^JtwmT9aXWLbI!=*RAlXigeq($Y44oJ^72_i-U zO)%afx;?4}V8j5{1xBM45i?3dko3BUCCbZ#Q%Ap!$MPWT!{mpCnvy754D2-Gh_IXn z`mOlPBaUp%Mtio+NRY5Z#iONONz0ndZr|q zCKzDmVF`y}A;At&$Q4tApd_9uYV9 zXwzKSlhbG}G>O5QTNiZAEd16;524etRa4^lYSUpDL*>&-m|F?~bm^Ix0*mmV3_*kv z2N%RP%i)4GNy-<5KV#SugeIY(5yZ+6XAnVL3P3n12u{W@g99=733sqTQqC_jwLuXeeB{-O>im1O$OCjQnoU*zJw3LbF9pu!s zesW55c*`5UXsLZ-auGoVu7Gl1!}7Hv$21v&2j3vT=Oyz>;N=0~8#Id%3aS1#xd`0#$alOcFWIZP1f`J}7 zn3*(fhh@wlT`gP`1GGgVojU%fa02w(r2BV8_P4IqZ@ zFgxF&s4o0jki~Iq@YzxtT2Oou9-eTh|Q!hh649A1p~^%Y+zl_cC`7qJg=C5S@q8Q=%r!tcD}X$hc1BV4l%KaW}^(r?{Kh?UvaDfiO0> zc$C&KNYRWhiVOrKCjK>pzZa|+lmciShzI|nGts>u8q2Y2Q!@*yvrO6MVlrf#i^-5} zE+#|s1Zu%>HBwPzD>8$D@*%{b(@@*Soj7m!<}P;QDVtD5qF}@(1W`pehG4W19b}d> z8E7)YK|gJtrMg`vb+8FQ7u7SFWW_5U7&Wa83BzX{3ju;Cuo5RqrepphV+t@^)%}M+ zKeZ~@&pF&t@p(I=0o2mBV~K*j*qWm5%epS3FhmB)er^x5Fqpe^G|E96;7Q+Hv&XSr zLj4*XrRUHHc{MDroYA>cra0H&9&1ZcevIFR^QmcYG3RQWmB(n5urT0hb$Clz?Y7@) zRYY!yOGW1&EO)|Ti5INn(QoNSq!NVHyaFFQdmI@6$DM>D9NVkfJ~j<$I7J;ZH7>8= z4xQ&C*BdYt!dlHA13o4IkM#%t%#vsIy z3E>6{+8XMjIil{G)C^PNCLHeb;H#q>(lgQTD{#YO>)0OP1$ z%v>o-QZuV9lNV&8F8ZEpN|dw6J<(kutG{yFi(5F+@a>ths)##K3mk~h0l2*px4k93lO_F_p1>SJPjFI)p>u{7dO{|l zCtQV|xUgMEPwA*q8br zMkPhGalf^o5o}k7M!Xj^;{A;>TaWk>P|)m58AMVNG$KvVh^wH{5NI7+o(aH&`jHkR zaj<-{KI#VXN_BRf4llbK8+<;z47`b>4*zAD`pEv#A*9go(Nwd6!J}Zri)dpRD2B;! z&5{ClG|tSRRZzak zE1$VIACn8%l>Rl^$~6q!oo2O?uPy!t|KNj z4V~6e61O(L@FO!Y@Vs3I3*qrnv*BiF!FCnO3H}OaS}J9n=tI{DKXx91EI|Py6?UTM zaH*lmhzdo2BQXx3LK@FNg}%9f7`P-z$ z6J<%-tUGdzfD4CMAckcJzNfNIfqPJBl?;;=k8v?v4c=cn zNf-Wl^VinV*L4Ry_N(`K>|OR)4`kuS z?30BMl29Df+3_A0XEW6SUgD(yNkQ}u9J>xQ!4`-A)wAIl0|&F079VFM&{D>yGN!jxnvJ0FAlzisVq9H3X>g}$}}t#d~Ihg19DhPtpSgg zTGo0bh$AwUaZW)~nLQRBCR}dgaiQ9Xw>mK480)QRdlYbk2DaE+;OE$%9w3mJpalAOI^f?2$Dq= z-u93hW3r9!GP5-7x?6;4EH(^b7HeA$IHhvhysu4!O^>O0_y|GTS|>~syhog@uy#feqN3x+$V|tF%!d24U9tT=26$BY;oaZ-&38n9muWj>_8Cuzz$T8t8pjxLo-z)x?cm^q+N!5GuSF676tC1 z>^kyve+)yaNIi+3;McEg{7Dkzi;P4DdUXel{zJI&@weOPT6&EeeasVT_g!ncdwWzX zttvX<*Pp<;*!kG}gk5UbWna^=iA5hYtu$R*OGk3xRWfJ*-4a1!S+>naKf?Z;#LAb6 zjL)Xak(!`wml%MR1)t!xK?~72|J~ygZj*+1A~^}-`Hp@Ej9*JVi0(+n-}~*Nkb;Bj zF^C&VDkdo5wr5m(+u%f9c_(lU+QFpi{cE7x9#umyCJN6{-VWfd_Xdy;yc0m49TbpN z|D|lc^cgj3*bO!;W=)sff-akAyNrtl(yhiJF}TiTM-bAw9v(t${WrAdP6MD7+CqZc zYtQU*d1L>7CK{_}B zKt-<~W2+-TDcXs2(}a0t6r8 zpiAlMCn#1UIauiF1O|JNl)b`LUX23og?*2!S?_#IZLVPrtRXl>9d7+zy~eXcY!-nv z{te*&CL=__3^{sHDOoobL_-cVoPKB}XX<#!NglAD(Vl=!ZpG!c0Sr3>$DCv>a%u*N zW5NYpdM9AxH*3|N;bXU{aiyEd;s#C{+1di^C30*i{5~u~AjWj#2K7aCbGT*$wzA$G z{&0g@f~OB{R3GBX|LTqEVYPnK!rKw+9l_o=+@T&+|FP+x?o@xT)cWv=uc@Qbt7Ory za9+5}UFu5pAK~V^)EZnCZBiHE@~2Iz$3tB7>W0Kw@xFMROfC?dwO;Kz9=hNx2fMb+ zCOvu?rqE36zTw;j<17MDgvPsE2CeH=ms*Ss+pC^ znFlgQ<9LfN5Cuql4gxwOjQEK(X z9V(niHr57dPkl15672~0Q`wu7I02(_(qY%C`zyr{4ZoxPgY7DPw_)7tEBhYaIev?M zSO$!u8@LV{g`0ZTGI(YA;p5ErmAemLD0d6WYwEtcKdqXg4l6`^&z+!9bd3y(e!1ex zO{fSV;YA@N@c3D{0U{0vn&veow4)p>nqgR=$%)IJDV4w<&jop?DGm;{QMod?3->@Y z7je121b3tlSLWhSWzlC$lYm$)HjEwm8X7z$qQ`NNH#u`6C0(Boz7FJzdC(G&Z-PNM z3AfKMHrP|{xWEt3CqYP?hBEK4o1|xvgacJ%5M)*0ss@r$1L9|M!i9IBZHT4Gurhc- z`7oqmG~i|d4^!YJ2;hpb04-6$ehe}S)GXoYVYoS#6SKx3d4``q4YmOV2S}LVkUNie zkSS9f>`q&9nhEY1oXS8y2$~t>&M>*%n%vCnGBeVLoe}i*ZgcP<%cS=u4iuwJgbKw!shGjg6)o^VM!-m-&rAdHB(PP-Eb;Dcyx| zqXW82I$VW*vu-K>EgdwVe-cCX55SbDI7x)WsSz}OnSvbM(gfZzj+g8ii9lwVN_v|l z32Y3KLO~blaM^2;nrHJ_sosaEl%5sqkd6?s7dnTk}8=QanbQ)euPB3P${@3?a zQbmRkg~JCad3#?;W*7#_=4d{+7$Y__L=)obqed9ucr>4G2RL#xA52ovT>~gHIGWEI z4PjymAk~2+t9>akK1HsX5}H`zI$+$4bUv7NIJhF9u_zRN65C~H(}GJIEPT`+9B42S zkQ!tD!(=(U#_cw0%YY2pK^Gbgp@;;8n=C?*a&tT|XkF*J^kiVjst*L)TCsw^)iT(HtE5A`dn(>?R`z*DIT`!P2enK41*gNQsE3O`^zJYD+^_(Sq%} z#Y`l&vDXm9PqB@Sp(3|wBgMcV=S$;saF9JhGK<`X}gPn5QzX^ zF?W{l@QD%IY*K@f13b}@Vw;&Ew(ByB*e)YORc5-m zvaKWm3k6C6JLiCq&-4_j^yEeF!|Z1f8B0b|c1eO=f^j%kwnHi6URV2sL+Q|6g82ly zgesx!66{_*!`Jm)qU*YZlxn*K0;i@+%BD}E+-4|E4caAQ!Y7f~E+NQ8!8Zy|_~;M> zufWI-R3IcfRT;<=Bgdd+6M#V^k{=1OL|wO_S8#9{dZnRq*l`mo|M0m?8Aua^o8p1= z(=k~<*Fh+`5K15&;0!{`Z3q1@6YGrrqC*(oUBJkS+;VH>mWc*nT*}d#%ESl&P54f=O)32Z?jPN<0D}L^VX4!K6fLl_2mSSsMW!ID-W4 z#047(eDl;{5DC{vDHT|e6CNRw$G})X&r6ZXh~SE(PMjz}EGBBP=nEE`V=N|y#3IZP zd5gtVEf%?nIL0Db#2mMp9vX{7V36t{!*G@mrZEpNfk14<$>b@NGdxX!=cDsjj!d4R zLu5Wp(qU8z8;TgOEe0h*kBwJRLyI6jj?viBJsTH1$${~+oaqd?@0wDOBkGAV0v5BU z{B$&mNIn2+dxb0DV1YOlofzHYtLl5{yTec=%mFyfojtifI;ac7CoXUZPtvKh@F<6E zLddZiaf@UCMpKZ9<76fp`)ZnR$f=5g2y{qs5~Vp=A_5oD_>4zui~;}>qX6hqF$x5g z(5QPO6jU3-d_aLkD@Tx81PNMenhGRGO{KapRBI(_gyT2Fjb*e2>#Tsz&Y&6~hEpv+ z4HZUc0^=uVTx^}CHn17HkA+Z`i**^H1Q)U_ev@P%yG~k_(3Bd>m z8I%V-b~3lqPNuS0C?>+=_HsvHvlCX{>jEF*u{vu_*3McvO9g3TG=ucPd{Sm9oc(Yn z>!#CK`%Zzv`I>Ichk(%Gew+xF13(1bOp?wpHR@!JPCNr71noq9m@X1B3W8`2 zkeeuDUQptiAGc0oo+t)7+B$qtYhgVpIZ&r#E(?xLn0fs{Fr~0UmX7K$z2*b~(6;db zFLM9|8FD3fVc?8q=)BZHu~=^|*qe$eAah`@0sOb{8*ID4T|taSJo<#SEi-5%(oi@| zu%-@|pxMvszZT;SXQb6Cd}b7c`>dfet^uVcg7X*^20f$dYrr0o?bpBzQ6q+|0ds~# z6gAVSuRz5Wj3<1x7?3s?+X3VZ2{Tc7{nKO1$;OP6RH?Lfy#%J=z(_iPx^Tka zy-tHLI-k;Cs%bIRv@irMffLYBjXBjD?FPftlq~(sZ^|T(h#TrYGUVTB$~9cY$d1VG z*Om+VttnRsvCq*bI_s;?$O4#z!0#j5tp?CuaMvGvi* zzBC-?3&&Ib)isgtHDm|05y;odXFt4e8L_3gxk~oJQUGD3T7XxuK2v1TZdA%Z=A3|c zZqS6g#kgWiF^(N_gBLNFu9Z13Wg%2teAHQ_MHwlG(Jm2p(PVf?1#6#P`?Q0A!7`F& zAmKg4E5h{NYG!cC#=wik5t4HWN-8YI7+=iCVvfo%PNPEEjG77_h>nV<1r0hE%*sbo zc;arNp3lv~utU+bR<-f64T!l+*ZVo8J94y92pV>oLNz2A)oVhkJpC z0)+&S^z*f_EU<{jmT``AjO>mJcXFc;^h=N{6PW$+g23Q*9&WawAW~c*0tFp+bg8l1 z1<9Hbhz1+!PxLxR$wnGdq*qV15$3z}ndnhDJZ`%vg|X*7$LTvkDNY@D&vE)rP+BHV zMHzbAIE5UfYOoG@33}OQ{x0cyz86Ww{__&bqh1u#D9|G0z{S1^-&-bVj{AjeK zxB)mrhkOP`#2GprIbA9L0}klI8xZf%0B%Pzy=?&d!$(^u1ldGAyNua4hb+vyDJChO zWDw$dpBabPwI{%^z#cZMIX0vD49D1EfECrd6Bh^=D(DcFXsXexP5blEjTumqOWurt z&$U*pN0ja23I}PNR2T<_$8-i`5Moc@I6)MINa2x$hl&YF<=_G%&176K3}FLi+T0=j z4a1<}ozDXh?KwABa!wn!pOZBJKHl|flCzD1aMf&0rOdcSoE=YMD zP0}D1^&B4wQu5+z0W8Hq0q9{-OTJ*;vLu%#*Ns7X_@etE|<)H7wx_ z)(-6oHB{F+xR8r9Kt?qbgwBy4FKPiaMobI7Kv|P2pPyAK<3kD0(#!HKAYxFq#oP7` z{1P7uOVoX691^!*B|a2jx^x zS{#LgEz8UcBD@V_G8E4 zRy)i*7>$d=lG>#&9EfEcx}(Upm{)i}51J;(fJaE>t^MN|{nj%atMqUUq6t_AK>A@A z8wpxdBQnhRI9V*(ut-Dw;B<(`Ko>4Q7hNG=Ltn@7qbA}wgJaVoN29=*7~*(G2Qqay zsw0vy&!PW&gjpaKCDK2grFW3daW_%7Kwh(B`XZYaP8_2bj3ux8!sef;G2v{j8jSZm z&MAxiFj#oH(RCDyaOdJY)3=(A&I1Mx4?GYC$n{-`{6?yRAdfLI46IF*J=Rk`rB*=h{ly7g}_ zJwT1v7vZn*mOeb7BNk?spa={8GvHued^#}0Wc}vIRg3bwHijqfqxKn5K*?BuDmLPa zLN%>#A9Vm?#LhuCm=z*lV_K{LYBHKTD^v**vUBG~VKI#J99oeOQD;yN6x~#?NLAor zJUFcB?c!EHay}e z)pq4P<&9{xsLR)*?&&7oDVf3_?WZzW<;Y9hk%@@OaWSsYj*2826VM){@Z3{uguNR= zQ~X+Tf@@NbwNog58{-$P7?zS6{b5{?+jTf!0(dllXDE?iTWuF+{YF_*8sQIL zUK%#Hyd0mXRilm{Hb3tOFSK%n(H~}(>Kc3eCcDT~FpEqSpgEKoFEQb;Tf4-Rv$nrH z$O$E%jTSx{q2i+90~Ehn6H;r?~D2W9xeCJ?d-hW@r zzc1!FX8unT^U)NWM+q2}KJ0x??W4XPe&adS&Z+Zr&#B|eSNyvpejzQRmGCJMR2 zGcsXPaGjK+?PT_W;VW{B^;H=BP~AADtoF`}ipa?@ zTz8)i3u~1aeI~C}GvnTbg+=n8z81T*Bu{GtbTDN!`%Pniu1?Y6+*d#z4~M6`g0KR= z35&mi613;5s!Kh&>Eo~Bh(tWz@|v2YP9UMSg!_I|HBScRVj3eM49xUa^X$mz9QiA5 z?4uq_ax6TuG+goxJv_W&v)Xmch5Ss;n{)v*Kf$V(ohyGi{PkuvI{)&y@~sOJFFX>q zepAibL1i}&QrX4dR8vzH#&Fz&YUKWZJx_||&U%0ZD6qi-k_if_@sSoC)f<0N3?TQ> z5x}HA=^$87e!WNqlh9mUY z^s~{^eIxY2Q_Tz`yT4LrBaArI<=Fj71cO;|cDN!Pv=n}IZn)=2{daGdVqPDqXE%KP zb|NUq6t3GeyG{R?yY|M7)}wIQYqY*Qz3Pt5NG@Ez=})8eMc4xrJzm_QFI4F*_AxAt z)w{J^V2YXs0(jOW{OR2AQ)BgBU1sr^BLdRk2voS4_~sIH?A=H)2l^hX4{fwkCu&l0 zoNhU3jjhRImFv$ip;!UDqCPPR@dR0F6Ib79T426BL}9fc`H zVAL9zIYn_OYzoin)N^bdUk&?q($n%^T_$z7kaC5muAHDhslE`NI6?2FR)ni3=y4sJ zACPn>_?Efx=9WKPO17h_M;-{Do1kad;?{(1JL#^858Yrv?gGdJq9}-nxN5oK5f8XJ zX(v6u9+dO@fzlEl+NrV)>o>8;Ac1do+8K!Z?b7hfo%M%#ePU<5N&S|ao2Q5Obm@=l zA1@8tC+bzBenxtFxYv+bPov3%`wQZ8#gVzsEGS5rx^=tO} zOM4x&i%CCX7yXIhzhH4Rh!e5+fi@RDyo)}>CZNWuniqCX){FJemWG#2*4Oj8=M;UW z{>jqtwkdiaLj3C~`jfnVcvt-~rf=L;U(4$eQ}vN-V33Y<=>lF*X$p!U3W{ARioIZc zY@q+&>^bM=CZgZ-zV8=Z_Rj8Xo86t6-JRVNBc`!`fPJKI94n$39uh11i5o?#?%~2B0H;8nLm)cPj9WT1vwViY0z3nVb zfQ=7pRp@2hV)e+DqM-;Vv`X>BC5oRl@dav}EGXbRbBHdYbIIaO@mUdVOhNrkzadp` zC7O%Ri>O;GF-Uw`M4MZQR@}%BTZyd!m&~MdX+did%c0M;7X7*D&NiZH;-}mxyj=xb z1FJO$tg@_zVl&7ab=}TuavK;4pR%FH+lb#V%N}bhnpIXZ&C2N=voYJ~jke-45Vo}w z5#7JBOXQs?K7iKLo)#vDeZmWY$Oc%>Z`FXY(J&^VgQx|E^~GLtwil<7?d$DDQ+R*2 z7bnE6B0AAQgje*6Ru)x}5_!>g9e6{_Q_dwSYXO_=s(pV<8;iutJN{GMFLxAQlr!}W z)Bu(T-xblhPU0mbT6T|Uh#ZgJgNy;b)%lj|=%9H^noyB1ucGN|bYci7`lb&=vkb7*ESQMbBzqK)HT z3*(&GiSHND;a=kFo`3Rq;R|y7pCV2<-TGXNBKObs$WydccWxAum%bxUVPdp=-CNWE zo&5WV?O`V8;)k@<+eqqRG%&9h`iR8Jv$)!9hNgHXG`V1BF@^ROleJk?&{tH^X4A^P zq6yE{(|tuH?Jatznf*kYYG%%CXlUk*qg#_PUg{^3{uT>7j0%)ox4-!4pK5jK zK5(#R_lX>BF0H>$)b=SEQ8}oVYk-(hN!6QGIVLcwt)to(2Z)EYV(K|iMBgR8h!zbL zi~m+ByUL!}LE=Fz;B&K27SZ9s_^i$uN*RK2c!mZJ5sB~?4H5g{wHYdgf)cBSicZzf zFrR@;z7AO*fG@=)cf`Q%=y0B)ABKt~AU7E%>cQ(XOk^X?iD4q%n})xmq60tBOouc< z!$m9YjHmN(@q*Ca@&t|)QCg^_*rJ`hSWIz?W?|p(d4eN20eY|Z7;64`de9A10r=z= zD?;b_ET!g9@+i?d^tUaid~}k+zXzS;Ag@5jf^QBvQ$?6IkNS)f^;&%4o1PmEQIG3- zux)QFbhaE4*7EOZUI|Q~;87yH;9#*&p;jUsOxm-c@PpGV>#cnjWXR0Gnjs6z#s!tc ztuW&(RTk-R(aKJ@$&6FlEPqYdp-j=OO!t|Q0j5@mIJO_R;+{6+q&JRjz^%ArW*ih5 zs}XF+ZN(io<4P)nlPN21nCoRTvb;ATUr(?SzGKFPdgIvM+KT(cjI(rdRFowPD|V)K z2j!^XDOrX5$B5L>+jD)0OjE{)q~!Tev&09hE)~ah4|RaSz|;(O4c@|@_)v9&K(05( zh?oQqBL@5Ihy7^Q#%jKA%r9mPh(_Jg1YU=rj5N_6FT?Cg6Wg>6)F)j;)l@|DP4&B( zDiEm_r;C`F)2hI-9Jp*Pip^@G^XQv&(X*Nf`kUl~DHnJFtS6#oQNOWR?jEIyV{vr* zRjHY!0Liqc48Tf4TVGZ|VG;N%yA782*t~vTl z*t=-L^@v$0A6WfrvKg$TUmGW)3aqXID>F6d;6R7-ICEx4@XH!rKQU#3ccz~^&piwK zc05|`kean&0)JId{MZZip8ioRH0fTB1hmXOzG4B)M1FTDxH!}Z-U zmDuVf=O%Lmc~MI`lu?s?ak)3}YLZ9J z&J+WBTRJA5U&5Y`36vQiI5Afin`K%BQ&L-lDJh>?Wr=$P3M%T29SQ<#=US;@vmD&bE$fsSc$m(c~C5NP+-33UJ^GvUkpJRhx5fWpfsBzcH4EslN_qnpG-m2Zn}S} zXccK*!VP>1m9@w=&L`-{>EhW)grXvv>x5D` z)Mm7S+qJyc+aVnHv0NwV(hO0*+ku^@SQr|7ZNr;Y3}@BS`2=UpIzglB;6>KEe|^q(8at2B><7i5s=q1$0j?9N_x19fo!9952Swco zpU95<#R|4LVOu$CS^_P0rijz7QQAx_>A}br&lJ_!-#JqZhyJZUge|NS)bt?;*pt-x zAj0L8hiKD7Vy!(!vF21`wA;gCaM`eZ4~wqim!0(M!y==-#Xl^3ravEs zT+*#0z4E~}KXb7_DzrB*HrZ2XQvp^hr|Ibe(H2XGp9;J?YbPkY5G%Gh)U{Cb3|Z!3 zg0Y?e-S!}f=9R80YVPG1j|x?!a~9nwEUB%{0S2#zfmd3Qs2ldU;`@0 zEuzPZL~_9bn-n;jFb5hdT55C19B57t_c7bBoBugqW0*~+lqn`_6_^wF2iBH|LDXqA z7-1QFlrdRxb8>!_t%0j7DD4#CrINt3rfd?*nT6SRO|^NwR#HVfOWHC^B*cBP{gk2- zAF$lD6o+#St3F26oTe-vZnU8Bg^;K5H&=h-4b9KAp@XN%UA%z1Je)Nw64rl!OOIzL;~ z3OcI#DtMHpQH6_IC6aS6sBh1uW^+V8=Q1@DlN?M9n4VJP{RjcE?>h$vSCPJPqt% z9Eo-zr~Z#)e!l3`;S=Wg5wNZ2?t(E_O~o@HWIi+_%wby(Llr|*;~`;5q9B$;*Nf@1 z`J$)z1ZPngNBHdILQyl|1#Y|} zxP|x7(vPMu6y>xhY2HH7T6>jVSSThouviF8r+riqbC!8GJI{Sg10KPK+s9P+2-ey= z>Dfm_f3UN1i?Gl?O-&XFm-uiNWi1lbttG%2TD(X!s(8p}O3azf#CWXel}Zx3e)QoY zF)?`shY4)lfailHu=%2C3g~eCs_3sqQ;6$fy#B$Kzl^wAI$xo}#UiYfKxHH6QUdj< z#iFIQk3L&09!j>_hzl#;He!6iiji%&U7MtrU0Tvq(JfaKRAIfWMEjSB#FDv!vjcms z{JKQM*Zz{ZLO8E1(9>9J23K%6FEZuPgO{l5Qqe)%ObeEZMgHr#{~`?v-y#~2zEf17 z`<97%qVQRou}riJn15W&hPn9YpLi5B?!R0#wRFN@%XZ@WELyx=M6zmka5*H&M!K+E zG^(!lI>6oSeK64hfYK^L9>qS&6BPTXh_AmxRSH|Hc==G72u2F$6I>;jGx7ET2Go1S zwBS)u2Zir^RMc;7sv~@}2eZ%TtddPEv3X=4PO@NO737ac#Sn4$JPmwILj0DmdJN>mk?_Kj9Se|U>BSBXJUR)2$4mh?B?&Vj+aRRiFEvP!f8n}{S4 zSJJsgN#`mi@VU7i@V^s$jq7+9W@;7WT+cmVs|;9kZCEXuxjyFcgOSKnE}YewHyMj} z8?3sTiv}zd%}my+OuT2h(Ds8L2g`hynmsN$BqFlV;j7pwtAubfE0FQ<+*3SUg)1C~ zxvqEA9E0e7;&IV~wUt|sn_5fI8sQ1qsbVdue{qek)JK=wNz1J*k@~jYlDAfL(>BrZ zwW4mq4J9=pWYBBY#Nig$bU>ZnH&Ge@W0Q#i6V!DgxzY=BRAn{Hq*=@;9Kk(IUY4^? zl&@kcbivM59EBD^Tr8cfEG_&L`&>gno=uArErkawV@fP!OD zl&L|hqCj;nP|3K;e0%|J!EaiCNr27AZWNJ?tP-6o8Pm*z`>UkzWgOvLP7iJrQGHBo z!`oPtC`)S1dI4bHS_AY}5CX)tx>W7xn~fr(wVA1(Z~M*XSFD^6anvfKJ- z#ukyvxjeK5y2^Qaev7ylQX*)pi0b^gS*o>MwI4ZnHbiTfF){Rc%X^r z^2(6ht=LE3OiykVEs+0vTX7-&Jmqb}Ww`b9%Qmy{VcSJ=iPTxMUF6!8hDEqqf7I9^ zj{C3WS>hwM61d{6cj7$3T%NtgOR59r#GOSi?G%sNUrjPf_^-N4fFoe9c)w&Ou#Z+@ zs1ojPHKKHsMFGRL(=6^KF7A|791k1Fa30@SPCx|!2Q?bgJG(?Q_E7zv6j7DtFix<~ z8Phw@Qky45i&nPj3!VbBcEmJg`Vs)+Av0UdA=Vus8GRSyNWbk#(L3H6)EF(`Con}Z zcl*|Ian`o8)t>h(Ol-fZz30}uMT@Xgs#;}sZaq}ETf{YgisQ_48s}l3-^c7Un8vmh zCNjOgTU4&UT4@iwLE&)WimpSQn&O1TR!@Kr)arY1K(muJ>=9YG6&bQu9BFprpZGI6 z3zxpsJ!YPA!uiuI`ev`_UhN0P7kCpw@d^GXAX0NKpg#LV19AOnnzm206hF+Oz57J0 zwt%kf11q}nG->;xk?o+w{Wu=nNssJThm||_L$6#*u?IwJyr+&e%6k6Y9a`ng10p8b zBpq&hyIwSZau4JFwaHzj>#~iYdO^C5+H-dDURjxbJs{pgiEkVf9km^vu%}QIz>bGR z$B+_j2v5pJJS8gA)4D=uzn`IR4#SjU$1I-{PK!xQ zep)oBzR3soIiKHq=)lv^)(;$|gB~19&!H+F(MG)U9Np&;BYc8i^oZfL>_N)&(cHE* z2WbdfxV1eBT@VRVkBY{cXU8K)1vhWeG0{E5Qd}^CopU@N9TTY<&)Uw3Z1@vR(#8B?CNtRY|K)nsPA!{6IzOa!sdnr|D@54BREyraa5cNK?kYM(43#l zVsTu@UP_s{uRS4Zgd8yU&diY0CqzB^>I>lcHWTMG#gUA2nN_su6b>nO zQQy-g_k@|^lzUn{Qt@jwMN%Nqo;#^@^jTE?c~MKC3!RDvh)IA7UI4U>UVcGLi9Bp^7SzXL4zH*jc{PnqYKL#>0yV{&@Aw&UulTT-{LW%?(p)Q=uTz6x!e+owTE65} zWv8=ZT=^YbgsqIea*~dn#ll|&x?W^faqX%;FS5HZ(cWS`?_(Hco zFiyWL>IOc|7yx(C7cYx$b#^m|$0+>x6xGjpgh7^1taLbcQ{VHVO2uOw2vZIn%P)|k z8Pug|=f!9+$M?>omL5vFAO^HJ%2>Yj;=L>9lN@UIN@Iz~^>9TH1-(GeT!0nBll0~V zZ>0=W>dpnxBhjvu;Ae@C`4fULSw=S_(m_%T=1h~mrI zn1zi+HP}CcN*V+xcFneMZPYzZ(E*p-D)X>Dds#GV_V-k5P6JBbEd|O6qxjcFSk1pD zL+)Ida|@-tE@Hax;sS%sN#-<7Z#1WGpk$`${*(d zem((uXN4pEkWAX7@S8G1kNG zAZLvqxeSdI@(_rVm{^vbLczD025HM;%@*vf5e^M5GDvhTc#4LkTF zuZkt1W?qqa#hX!oy9!G^MD@FdEm-*TuR%+Kf8rYE6MW}&@KpFQ*J17mf6{eqaKJx* z9rFtQpVvi~1S?BD92@};i>nV@hLp3}xLtWY5-)>l7Ts=N@XWX&f;G-#?nf|{g#Z3W z;w<}5d@M5954|ZiRJT*(QO$6P4;OgT)0vxMBBRBAf|(3|$R}8*!Y}?r#5VKN26Mtn zVh07Nhb8VtFXK}&vW8vYQK`On>7_Pue&|yeF2Xw!GXIqK3CPyqQ_jc2u_g7dR1zKjRBg(?a5h=$-Ze z^>ZO|_ZOm3Et^p2&3dlFhxyHql3L&TLTt3hkRBe)V`%M{uu?_hAHIar4Y<}$9-}-y;0sGZ|F!8(nAl_iO&X4Bk@BgD{&+ubEigDf+<0Vv|3YGr}h6gq= z_$`r=x(@nDOy<-t|0G^!f5p#cDQA8b&oW%}AG216|6|rF?iVPa2p{(g@NX1T+^;ab z1w7|h$U6ADel^p-`>Qy^@W$WFIeYatGrZjIVyCw%rE7WNcae~6))Mb#oyz4| z4%xfk-eyGlm1;=xA7T_2xaJS>X@H3oE-5uYR;N3^iSitUvTM<+xA86qfZlg7vf*dm zF&nt?j(CvaT7P1z0{-AX#X0sP{u1{y4=nr(f(ZW6zr=V9u$7T=bj(fe<8WS(`a^At z;7jE8Qr)g*sV1&G3}xo_g_T!jf|mxRyQ7gNmvE^^H94I9DnhDmxh|v{EKVsubb5T2RGqP0H?dFaQgy;SL$L(RD ze&0{l_0DKK0a}Xe75t?lJBq4FOZ?>p&Utu%Sw2cw6K?edub5RwYS@y%@mR?a>>MZ+ zN1GlfRS*0eDAf>a5+pA(#qR{kj^fi|as|uf3||SBm&DCt+8!eBXWs~wFg&K{P^pY~ zM~2F{I$v?m@Tft*Sf?k@s2Nmin|-0OWstR*;k;E$Ux&)%D78RR=TMwby^FyMDC_J* zs$+wD%gKE4RWY4>-@H(VtDp_&uX55q@Og9Z_7k<>tGsHJ@T11%Wqn5OTVD1lvB8E2 zaX~CY^E_By#>9Ki3QBK(n&iQ%?7iRA8Y>EV$>CGx8p~7J3A1JIJwEkWA*{&SSCEZ* zoKZO88Wg5EyaM$~J4ofP%&gwR@&YfNLiT=OBI~ZN&77^RSnN2dV~eLM$a}5bU!hvB zSCk3%Q&T>+e5Zd`Rpk2>11rj5;)`Nl_-^E?1J#-rB-~UgZ^98D!W?eJaam z*z`KMiX14uEvC^`WU~<8M$nonvJ1NRMip68Y&}PRR*|g(L2KT5b1k5bVX}Ukg+&0( zyM_VKmf2h*+!|l=&7JZ9-q!cGH_O8V%(HNn!xrSw{QJo^+f$1C7w4yKjY?NeSW zFIQDr8FmbVs>+Qp%>7SQIS?z{p4H@w1zWkhTzD_cwc-WUbHDMZw3n9Paa&D{dcfFv zcZ@@;RUQ$*I9v(?0hjGLbjFOaZWH249Nv|8ZCXsrtIIWQbFkI~Fp zGD15@8*0fgct>i|s?)Tg#Q4hn_VkrTZ!9>jbnwZ~|3q>O~OB2xC& z9`k%0DF^rm%zjZNUqtPj$gVy_-Fo(}L_KE`wKkLN?iinxbj+E5z}Tk9p2&HarQ2Os zfT4z=r@8i-C`mxsS{lX1cw?T*q$V&d3RszdR!&zVi|#gYn4FGaSEI5KcCUiI9EDqd zSn&M!iOQGF&)Qc;9U}fA%3?j5>szw{HGCVuw+>Ow|7B`Ban%2&Xq9*(ivFiI{cFX| zqW_7!;o;%-TRRTd3bQ}wdcJKYw~E0QvCDMCUHZXivRpC8RY@8ctJo9GjG9Ydw2(0Y zKiN=3j69|l(fcv7r?!w9#e(TAq)D-|XPV6d4A;%)?=s>I*A)|^E@t9ScgJ|MPz}XM zV9YkBu{9Le8Im6Yd?|HnMi$I=l;{5>6;hQ)%$f5w6ge(ySX&q&F z;z1kNOzH*Aq$cQ1ied2=4rHANJ zN^pG8RW^a(@b4yH46a$4OQLmwy+he94>k6DK0wyN2zg133yUXO{6lxm->L>XzEU4U=D5JwrrGOBz&Lz&4DZO%T|bp7 z27EH~AGoYn$lwb1f6d&yiT;-6pLps&rts3az$;;NyCNkbvgSY3-kbX05=Z`5iSfX3 z4UpEN!T(Q1D=~c+>3|L?TdM-6Esg(?hL+gBOMX5_#5&@YbWJ^Gs;XxA6+ar+V6h+@Jhv5`;{{6BDUeHLehy~;xDH~}- z^GC`kZM|pDNV!wMfOlZ3?AUNk;VIVSmF$q-wSd|&g2Z!a{1p&BU6{rGU;!Di$=@Ykm(i1$humgCw7eNuZ2It+!0JkvJDnA5SOF17Urev z*+S2jG+9GKvreSTBicew{#aR0XsbPskCzQJd={^gLD0@jSDDTX~R)Mbsr9?=2P4m-(`l zR^+KaMLw@ZlxC9P|jjG_c;T2et=g&yOM($RXvIFzv$q@z_8hz6QL67A%n6{mh1tYd#Ly zuj3yxr|MLDp&X5x7A}<2Wxy3vMOw6KiG0Ef(34B#6g*)Qz7%TsY-@sehAfqM(`tnU zn!OdyY(a@+*G(%YD*^`?R>V*LWyC*abyeWw=~1e^E+-{D232nQ{^m6p^SvYMPK#?1xk%QCDBO0pG9fd<@yRFDVzXx^$p^LC zKKbAb?rvEnynffLbo=gFxS1(m&E9Mi-)dU_g((E@m0jOK2iKwtHqoDJWn(k12%wC5Wm0{VKtLtRl5ObI|#rYbRO@ZrWQbX$+t%jgkW`lfo)QWbh`B}}mp2n@0 zO{3L*m&<39N-_-ttg&ZhrL6YAR_Ww=*}9iXf_;hw7`WEnn1SAVS0N0+8|%k0uI|^=B-WgV${}B zlcas;H z(zbiv*dp)I;8)uw+h_wVmS!{5vRaQ<=>cac%kBO-jc3<(nV|)4R~;jqk5bK@vW5jp zfbDk5ngwR8;e3=UUxSY=U=OTK<}-1-`CpqjL8Wm-DV!BMR4J8g93K4kqRS-3-&nt@ zc$K?uAkjBFWe4z&=DV<`uz;rQDq$lBcgdz0wIA-1x$w}VwJ7sRES+~y(UWSPJpLpa zy@2FyS>(TfD+OlkgoZ_jc0&PKK=18F21I^)WICYI{HoJl3=r~wU(L{XI_{Gv0%o7% zOu!co?w6yqfE5-<)enNx71Nf3$hDBpAC$3SCJkWJ5{^w&C$B0U6P-jAo&w5_e?@^U zVGnbFmTk_w!?K$=TYBu$wnMTT#Kx^dvWoZ<)*2gRiuhbn!#lnS3A>vVDhaE0L^i5^ zK(*j+)`dHF($FKaZ<5&v!?~UZgwGpbcgzUgj0i3VGY8el3NLsUEk6oNjrND6L4O>P zzrlb?*+&L>WN^rHO45MEVuzn1JaS6J*Dnj zCr$G_=aCboXY8}`)lkobmu0@BS{Eh03Gu3IK#gCON%VRTy&^sEs;om-UyI%>f#C&=l@g}+C0II1&D4SMT!1D|m^9d!WK zqu#ys;q-EEJ*2=D>U2DeuD_yPV72T&Zl zrPEOie<_M??h}6r`9=A_Pa{3@FL3nmb2^F;F%d2lj{mp~>;mAvtKxK2fzN*ob2wMb z2s6X&3cZN1hHw?(>LQNuqTq9-qT!={j+U0+#fqO2R$xZVM1UUSud!PWPHKPmvwz$f5i{N@($-~6gh^N01X zoyk=x317GdW%t*M z3!3^n)fhd8I3|t_@5k>T8y~)%g#FXJem!M|c+Yh5s+~alwxMH89Vs-@% zf&fgnQ=wpqT*K{fOthL{_zW}G;Q2EI&Th$2{AL0UhvPqc(vQIJ`{72yx#32^F)w2( zrNWJdDRTL_~y=vFE8zxF9;8gS?SE?=? z!*=|=hz|$cW3p4t59UnB!V!r4c~~umV|@FM>@#^RrGaO2@J;3mLzgqsLww>%TS zv*5DfCc)*vO@_;bYoR+G+;#qFE^{lpD{g}3+^9XM5OX1}5dU#kT&@RrcCOR02yPwR zUbyGrF2H>V_buG-a5B&7s03FBt`Xb=2upz95w16!8*XwQ#%}?Btc2SPw+HSp+;O;b zaBsj}hx;1tSGb^jr=ucVI9xNh)^NSyhQg)6O@^BZHy7>^xQ9^Le|0R<{}ti?5MO|y z)d1<4VvXQf1)W^c)Zn<>LO5n4N~2<5{n4vK^_t{-Q#L9H0Iz12!sE|?n+<1=={fj4 z7j7P${m0{FK3pG6ny&DdVz8F8B6axemck81zJuXEf_NT^tAWq#h&cv#_sKw~18l3n zF&jB@ru=6YybQmW!#xTY3`Cwh8JNAy5^R{tz^lu^Q_&ixh>g#1MZkYRjBO}Nf8&}4!8P@PSrYLivs>oDZ{+EPkCuUtS@3pr(P;d>idHhd4PRrJ`^XNs zUIu~VxW#sS0!YbyX~W5YnUprn($E*)jX2)|_mqM60xs}HI8X-RU>TUzKHmzk9_d?P z$1?E#GU@kO@Nje~bNf&Y^ydoL0p%xq!Kk?eyaT^Wd}L z-hd;0;0hXO>|1~n2);0LLSL9Up)bsw&==-u?hBs*%oMUKa0kDAE2I~csWEd(-wc?w zGHmzp_vqsWKJX`H;8!g;6lw%Z`#HfTz6~z{%mew+*-pm_xPn#q=hH%`<3DizMNUU` zxW;fPa6REh!cBym0k;Hh4cs=kXW(9gy9)O;++T3@XF&$QO@&(w_c+`WaC_jMgL?z+ z2HY*U+i=ceXRM=IvC|O`=Ys18Hx6zl+&sATa8JS=fjb4~`0KW$%OmwL{lE=**7Ma! zy`rDCh45;8G<{Ils7*2B^kJfA5r;IU{d4t>^vgKCDn1->W33)dZN}@Z@zuT)8G3)M z4po?-FVX6fbEQ613#UyJ^kyQWC7vkL8_;VL^>SJy70oiDL{!9Mmn+tyQ40)1tM}NY zicwm9(&nQqh17t5Hx&(=P*Dw|7In+i%ZWzKOkhf;?i7tppkhn6+nqZlFW;S;o|>6C zX-tz?6X|lMUPUx@F;J6amfk>X=DBAMj^?#!u5?#AwHDV)oAdASqD3a15=I1NtRxa8RQM5)31RB^JTc@W`ooTvX-PokW_@vmDamk4>EmM->6I-^7iEZ_~=I-Bn zYOlNp+~odVkEJ`W>wSV_W71}1WRG)ar%$JY*?JX~zWUwLx1J;1{V6NJXwx*oo!B)s zJ1sN4Tkrgi3GSrclhV@rr;qKOufW8t)ZB^bxe51h#uV~~-o6FGW3$|`EqkV?j_s4% zEjvFwH#;@6vl$%MV^HVVxU}3!Iqr$+Gu%^CGbweFURl*7>~1w_Glg7rjYd4$+EDm( zy@F_)Ytbo3NHs3&Nm)3avGcdHR|=pns>xW~dh@Hj%ge^^fuoh_X3LcOcj zg^=)P8d(I?d!OROPtg9^dVg9mOK-1rB^0=Y1MZ^-kLZE)MzNl(b@PU<5Z$-X#SQxB zZF_Z$8|DUW@NaVe^z76u&@nNWf28;B)O%{j_D;+v ze5|{Nnq8vj|1hik+|)7Y?#$HD>6zZFdeQe6^lg5HVi;}TjXB*iDiCB-EtC$)@Cib)xGl!_MWohWdjK2;k;g$woLV(>>Cb`KRlqHh#K zZkWLMMfwJ9C?j+x=MwbMfW>-F$gta}Pq*Gvhvdb$>GB@E1IHZ_!>vStOZ5$6#P_s* znf@s)U4|!X?x%Ij^dGd5v~;=tt>&hOAJvD7QTI{7qk4~^BoKZ=-lXi*0SXRI&d778 zj-E6nfAlnptzr_wk1jo?C(`1_^lD z8*BFBxChFjpnm>zc_nzl_{Zw$;$;z%G2?XcfOt>a!ck`XFV?( zJYea3y>|HocftT3GzmGWxq0dNG;*Hal={pA7oWnvQ^eF}wZDgr}BUrk~hpv2j}XoC@Dv8DQ2`VF?!F}{evDTv_?=YMT`KGeqg>nQat##SwFL+ zW^IG)S^rQ;U}y30$Mn~J6j5%s{-h{)i-DPxv_~I~lleDINX`4|dvzFe;m)i=_;N4g z(=58PUw@b>7C`6s=|gd@Y^6}M|H=K3XtNpT8jhttJfOcFG*`Q74KzBr4zjQI*Lq`m zXff3O7EkHpa0)?B>8Ish<=N@AJ*I9+~T<-`VY(xI>5pLzvDFR5znos^-v9_ z67X-+Z%6eCaxv=j{nPrdAuUxqG1ZgZOFR{x(fu@7M8N-p+!#H6Dq@+Z?sK}oCYR%B zi=JooZ^`c@q}!u(<9Yo%lh1WH1=jNzbw7)MjMI91xk5W^qN?_8ejfa4r3d&inp|Z^ zfyV_>@LANFJV@C>ldH9#Y37UiQ*`pEp3KA79~Foj=zg3=ozs7lYj8?jN>R{h$Q8XI zc9llm&=1g*_w*L}daY1U>J5EA z4gUbj!b9)tP2~pdYZC)OZDB0>E<=Z@&xa7`n<(chNZewTZg7nTyEk*dZ>SH42lq|Q z&7Yydx0r~1KerLL(#RY7?{b@V*33NkV?9i6x1lS5a1^upf<8EKr}iWSA_g*TzNXjm z{PC(Ds0HuR-nNEr3=iEJ^xe<8LG2MoNtg6hwg2AUC$-gPHhFH95h0_RDT(g*LFr?Z zKuvZh=H=(I2)IvqomuF%bjK#&mylh+lXg*WT`exoosgHA zF($ob1|nNB2IAbEDfVZ*p@`3*$y@a>XXli$NbY8oj`0)SyJ_Pa`W(3jXKI_@fPR3! z8YlPSTK2Up{WVJbnRS=(bR}EwY)o+P(~@b~B)xH%S3)IB$<7!%W50Hf5*MkHAuf(* z>7ik<@rf~U@$qqqF)@j8adGj<@kucUG`vEpH*tCs$ERnf9n?}(mX*AriBqz3QZsU& z(vnm}1#d)44nCx{Qo)tI!AV)E`D4bX=N;DCs2IxKs#gj=qTPkzggmV+G-qqCyex`1 z0p^AI8bH^}8<7;fz*axvk`j}WlapgxrlcgbOo(e46W`K<<2|M9me#o{-v#w39e-P2 z0Nd(Zx)Xsfc`kcLuXZ=QXFSin!!!9=D)*KC4lD;2QufdKXR*CHDm~zz)POF3Qv)dC zcRf!W*Od5xdB*=JA0Hh+a_Hx-d&!C{sQTrKzm-- zE5lal1C}@qXyYgP22*{z3sGLs9;VBrH=;|Q>cN!vsUG)_sAoWGWZ0Nd+bgWQQSU4{ zKhszEzo!FrJi@mdP>6F){H;$?+*E=c(oeeI_0vYyPdCgO_5L-_mbr7wOa${Q(jK?$#n->&l|M&j^0YYqHWix_62l5z>i4Qo#1E)+ps`c_sLe2^<{Lpqocu|f z;2q(9H0+9Aze@5roMF2sWv6%S=l)qcVlsDg{j?|8D3Jfr9yE2dPkw-6S+%@TP5y!w zsY$Uk#HfK=>~tr@m~Z^1%|K^HAJ=2(nNZ_i`Fkm{|6!6{-qt4iOpag58Oh>~MzQ6M z$-#eW)0GT(`H9(G8l* zFO`he{tHD*#qYJsMmPw;c~SYLa$^RL-70_sm>bOJKwBDl^RBdwuTC77;!;s z#X<;OtO+z&uEAH zb+Pp^nV-UdvDbh11mLkIZv0TX+{tJ`H5;Ip4vS#-ncX2wvJ$<*jc3dOuBj z8W_pagPLDzftokZGs;o8G&P7T3Uj)|C(`ch#-T*7@uG&DDN z_B1qtAxDokGzQ4$L}N3sX(ON<7uJr^;6_F{c|tT*aTVivmnbdMon~$loh%z}Z62Kx z&CR^7H!>>X9V`r^C<<$Al$Xz=fodzMQ)8pH_5!6gHX7?^@S+zv^NiWSXT?x%#E`rM zH%)E^<)VEPqlNY&O=@B^#K$eEiIM4l&TObTm>M-TA|RR&UxPrPfO0LS3Pw65ihjU6*37n=zA?zD{D z*X$mTj>e?FBp{4I`YQ2xGxAhv>YvLZg^Q%q4YAF(DjIX_bvh7jw3Kg%R%Tc6c%kro zBcyV2M)nv`Dlh$tNH?1sJAt(EXhN6fMkhREa*vrJr-czn`ZiJ$ zV*(TM^3yW1-$5VPVHJYk72V7n(-i@0;EQ5LL-C%V*IGb&y^ra0I^RgeI|Vd3#^~t( zp=hUSttRF>F-9$U6+<*N7Sps!tkF$e6ErH;xF`6!7-weNQe_)Kmtu`};)Wn+obj0a zNGzcWaYkqQDh^ZdV=<4R4m3U9=o)-e^fFV9w<`2WJS5jA0_!lwxv!~BXctCt3s9!R~W^}R3`=pt@efyX)UJ3%*+jj8f` z(UW4ng*rYX#lR=m1no>QrU(BhdYW~Lw_4G;mC;1}1S+;N`UU?ihI?bt{|(8%4F-X; zwc!l@j~IEEsO;88rQlygzrRJ*rc132BN$IL{4FX{{04GkGg$sEdQsLmNRS=j8v%64m{vb@KT0C62@ju$s6A>#r>BR5|p5X2to?! zT00{xs7NN6MfanA=_n(sJ$URa>8te=OQrQ-LEo-}ks{~F0=m<|c+lXknM=QSFy;l# zlXus)uLWt&_1#J|Z9CxQ;xGV#)s=%%MdTagVV;E|z!| zwWHCNM%`nyrVaxPf9l=E@Pm9r`t}^EQ$QE|;#}oYxyXyH!h6bPatd8_VN|P0SlGqr zsV%3AU5p7>=qKN63~)XsL&l_z&aijCSJ1k9As|-5oDFM{6l&Mis3%v+VW#@Cr>jv3 zBJ@;Oi0IW+zMC;H;BncN$<~Y9`Fb5%*v+V~t)TN=@o(x~#;#;uMrOw7G4Ay2yeYZq@TO+uPRiay&AS_QD{htsHqCPLpOAha@tA38 zTgctrsP5V-tEHvqjmgc($)A+#&QBekneGMx8Zma8bo%6~3fwNs`vh@ScF?iz;H$W@ zkK77&$*`PBnVD|>M`fipnI}!lhMlR3Fiirz@w;VZu69nGJ2M0422-**R14Sw8{fwsYGnYufQvh9;W78M==Od*J&G`)P9zqYiGtpXy;$f%tr@ zhtWuTivH?hG?9n!fW^pBddv@>!T)G6p5}f+Tznc9?(~ zR5~upEB31hu6~`MqFzR&Dko(Hfa$rpb_{-=qFud=C@>28jXS|{Y7Y5!%X{1fb$|{)gs??dXugM^{ zd(zlUH&gPG3~{3bt`^5#mVR03S!1$tUYEffo;zlIZuXSSH)H@qIb(8WT#&8A)OD%zkh~%N(lYtSN78SMiuhRiV^H!r z*G(CkmXVh;DKFhUX8e@wiJ!=F05Yb#>!4v+|BcK3R948a&S2bj*k_VD^ui?yTVSYgU-T^xp(GTm)uOwymgAVw$beYShuMq10jWkyTHx@x@ z3+r#x2>MnkQPa!Z;i^ve^*5S{Td=w9ZzKkOClfeZWQ+t=>FxeTHSxWq@B16W#SbW& zm#9CMV%9&I%v$^`spkMAP5ws?rPsO`gZw%sx_^<$)NeEtRdw1EH_)gef0ZfRpppZi zD)Kkk*o);9ze_qe(6~?jA^TA5V51dnN&`3UItXpKEjy?-FCUDNJa-UO>N~PCEgxyb z&?|$G_n)#mT^)=yZ_r?)p7={r%wVWqg>+z`(ZO$5G>V37(L}<`n^-=?kPczWzhQa~h@ikIzX>+n~2n zfX`}jBi(Wvoq{&$?alTlQ$!AW?X3yWeS2%7b$JHyBJ{>!=`R=i)8JX#6yEv>CO&krT z|0ETSMi1_$Q=?IvJ$fs%Hpq$flISt$!@cAhgF&zl+XNs`1Z^AxrD8wrA7k`_=(sZm zBK)Aw$5tg80~|ewp*WKw*Eiq z!1YkzSR-B@MRSyHIeM&7Dd?D8vvjUm!?2!{rlse0O`jf*!TO9Hhtb8g{s0NE;QDT? z(N=qonvcU;^f*m~7kEOq^khm+fuee49OT7G`gI&Q#wk6X3MU!ykRQ{>8wuL;v=3gu z3wj@J3zm=RMr|IQfm9&_;^Pb@W*9E|HUlG7Qn?AnJfXLy!pWG_9kQ@)9g%4S3S%yn zpMaU4H_^xze*MTf0Wte07%9RZr-2OoF%kN2z!Xkcm6m20BSm1g86zedgG5kUZHaSfSF53 z#f+>RXj#!?r(}S*&*>Y? zvPag(Ii67ik5Qdo2yLNZsl1c~f<{vF5u-+<#-*avtqs|vRDOzXO=g=)T?q7aLWh z!h@>{Z+DdRwXEjw_ZJf*I>26*CIhj4J#S?r9Z|us*DRNxIx#(Z{G`16Xl}8Fzxs4} zu~Dh9(6k0zScFyU2uH(U7;PC!nbV}EH8Kh%y0`+hScLjjQq>HrT&kl@o`}y`$0Eo6 zVWo)vKkD!QTX(nrZ@as)?})NzjEv(^(98&q&>S^k4^*?J0XwsI>z5ld31d1N8yV4T z5TU&Xb*HQzXwYGN$PIVTt#p zzSpXIzFuog)Z)v*FebmGrz)4}xhj4pvBRtmTI(m9dDg852P#miU#$Rp?NK{KEefJp z6HLvj6AFs~EF~aOap>h22ZBpnjqRwY0O5(yfT2&lOkmM}6SsH4o2QFL6e6PyGX z|L7pqK+rgY2wb34P|;CAK?vX|j#~s&P-K}=2Ne;;1r-&YjW?pa&-ZuEy;ao<32yJr z{7pmM?c8&I=XZYl`JHn=_PNoEKR18-O_hKYODb&E@f2 zy*fHCECtyW!+Rb{$+=-zj&+xQ>Hkt#ADKIMB&v7*(WL5|AC@RKKyz^%)_5(PTQ2io zups;8aPp^fM^tPeqJ~l!4b0=;=-hCuOiBGK*?;<}1+|f&rUrvx?g)?vL3u79$5E|3 z7zYuRm+RTak>~7P58}BsZj?%;pjNAe0|T4{!GQkbNB^RQ*#jfbXze~yo;!CzxG0Vn zmBX+em&@gWfpTf_Kxv>v|Kh%IacS|K;ZkXCIF}v+X_-S9$H5*3oxt<|0Dn)Ay*a#? zzrHvO=JdZc+P@X1lao7wYAZtj z?#?ei=8pU7=6?D)Z-={c-pAc>$G^DaL3+4aKWp@}RzC;pXYwuw!-wu_xsxkBQ0~3$ z{^}8beCWHMHjqDKzn`_=NBy%~?dmrBz33KK+rwXS$H~cC?Ec?bUmv;5-968)&JU~Q z@5e*g+vltfm$pwRl@5x7y_5Dei~1H-mc0DMuiO;u)3mGh3+MNh<6z%LcHNw2G@fu+ zT($zsnmVt%{FSeItC%^2)n>PK`Uj`qI2QH1mAPuV*g~`cFntT2{+VZ*5r^3S@3U7=~`BnJp zQ-4r8Km6+-g)ez5e0})i@RQNzZ-ie9zaHKj{(q;v=Cyxw`s;og{xY15E{o2J-X1*? zzCJo9`uk{W)Q;X2T^_wV`hMyCrF%=)M>j;@E`2Qecq{ry^x^1J(buAXiSCL1EBaRS z!{{5Ocg8=8&Wt}AUmR!g=c6~o*TmPx+v4r*E{Zzl%Q<|3mzR=>74P@kioI z;`8J4;y1^)M{kO+j<1S89KR#JG`=i;dwgSjZhTwx-uPYdyW{u7?~DIFel)rwzC1oB z{y^N0KNw#Ww=Rq?h_}X{h#!i79RDQ#Y5Z{fv-rHy+e&XOT~>Na>7Pn>mp)MXeCZ3N zJ4)Xy-B9{k>4&AkPk(0kOQk5NULS{1Ysn=4g7ormV>4Q6=g~U-EjiQfU-`kCCWDFb zC>RQQ?lj}HwK@u-*19Afk4{ZnM-Bx^lm@G#ZDA6vS4H(T(In^fU!Fw+tD|!{PtOl0 z_&wKtC&P*QThHcM60Hc%57WoOBZs2A%y_hw^5fK2|C#{denqcRLH!%L5v|kX$!CSl z_;59CFT`nBzgADHJ;1tGD--D>!J~sgP_@c>O3)pu59{B|4x9Jn*5%=ZL&Zp5&m&h0 z4fQW>rEa~SM9bpy!!>cm)xztKd|(G9()}Hvak|YAm&EB??4*8|d)2pkkE?G|-4CkU z!Z>V2XM}z*T4{Y_GjI?jLH#ThVn9ISWUwNrrxCdJ?FQ*Tx^{mVBtiP1tMy6`Ww0!+ zG{S^K(1_gY02&?~)RQ<7@EoEwvEHov4p;9em7`iKJR{8(V(jUu)cN9`pGlT z+Pdxh%Xdt+&Tf=rwOw8xJbNgJ0is#*Wz+l5m;h-4y6c73+0Am-<*`E$LHCVw8fD#R zo!+QWZI}j0`L$Q1@vAqcqnp4$x>+pL91x|J(o=Pj3>37QRFX0fCH?fj*-zC|I#O%2 z!fFDVsVrSIk)F1t8Be4G6NZZ^Wf{|r6s+{JL4ip4FrcPjSyWpSmE76rny99;@o4et zXq1YKfk}6znI@+7w9+n@xtQ#RSP=j=jnd?n33rCd9FTSsih^~!RnUw z=KZ75z3ba0Wnj!ueTWh|T6b5qBw7LoO`TK8Nt z3OkI08h$DWga^e-Pq>jMO>5-Vo<u#;D3Pl0UShFPZ;cOHd%H^D{6Fo7A7rQ6fUo2Ejf3r|nym(=g-y0XM=J%%y$M~)6 zzJ%Z9yC1QqwJ_ovSZefS6^aPz*JkJMuA#bh_kH<&cy|rb&>~gI`J#L*#v^+Vj01>& z&z1cfD|_*x<^is3RnXJlcWN+#>iY)5{#^S!9Bgb>oxo!t+xMRy?U9}Ei@xl?79BEj zy^UR|QA1Lc>epoTWVE#*sV2j#qr*6itTrh!2yW5eozp)o{Lx_{)sLBGxp2o66aEG6Z% z@@lH(V$PEMZ2X|>A3aLZ67eeAKv%8lAuN;b;1foduVtP-4x z#&udj5UIjC@2K7s3f6<9uMuZ&Zp=&KMx|DoTIie^3dK_ky}q|lLp4R!gpsMA?{$uE z^9`Vp39-K zIy#0!`QYGa4g(rTBY4PUeC5mbK^_`>8c1TIK{1_`&tb>kXn0ZY^w? z^uuORaJw>5|COYwdJ9O-(Bf=Vyx`@p@5S3)#p%Prgk$55<+QHHbn=7SYybSm!gv6C zqc0Z+_hXFix;QvK{KxF1hcF2r(|Gj@wI>eZOWp8vw!S~96>}ZP$3Uk-0 z>nQ`17zNVXZ)QuD9z5zaq{K-<6?bJ@md@YvE+9)rXTx0f-la=M^TAh>rDV5s`KJ1{ zH{82)BHYace+}-Akx5}PT;HA@x6eLkkk&q@bRPDJo{}LRmiB!)4>#^R&hJJ09?$R3 z_8kqkXZ_>*?d~OvmF%34AbGMs9+z@+MK2joL;^RCuPtufwQTKtgCq?bv4dvNH@N41 z3$v4!l^1j>94bUvCGF2XJ$}HhRg(<&R*@aO->w2MudfO;>IldWbS@aO`TM`>xE?RT zcssre!`)LcgIIVSj$(G^cI0kMcJKas4_v0fMs)u)8(h8^i*4WK`?j2>_M~!`+2zeR znJ=jnC%di2E~=*Y=iX5g=PLbOsw${XPH`v$CktG4jnLU>rmn+uCic3i1@&P%nP<69 z_P*yeRJ%ErnB@F(j%ASZ+PNVXp&ZM=982uHg4b}D3dhoyF+UMorEn|>+UQktEL-Xa z;*RH&VPOt!mFb-eydE}kuXY^Eo`-Y<33|V(OsJtF-{oyj6O%U7Pb)mkKyF*NhZ%Op z+8_>d4>K%0%g5ysNH)KJJc@V>o+cV2j0yszy@IMp=+^1nU}`8OPTzzf50 zYj)|%z2|!+pm)6v7)kHCBm2tAeIpK!t(>RB`~z3&u;IYx>|IE+5OX3-2J72ZeY5ZI zq5j-uqvVbQ_d;acf8bwt1RMqd<@*qDuXwTq+#OFIUojJ>g-QQv;$mS^UL7s36)KcU zmpo;ap!oBr?6oYHWP_4qgThcU;32^Xu5>~2uBR++IUY{2OBUCL^JlpjZ~`X@L~A>c zD{CDJA>2Z)*dWb7YwlH#@?LH3Z(7T%C)9LM8=JQ(UFQS&dGTg;@~VS&($OMF z09x)^wfpoc{%h5mLhAS8DqFT@k+6Bhn(-Iq*d&-O7h772ak6nG(h%hs^P&(xc{N^6 zO_tr#8V0R!-;vuzr#c<^(Luj!DEfB+ihEc8uAwj;wi8@@am_wL`xn=YWkYL+b-Kse z@fKE+QxlM~Lv!_eahS)#5Dy!h{jPxb@%v?p!rCay3>z-viwW=XlJLaePOG6ta^qj%7=>3 zuOF(CH_1!+Q0*X)mk$*tdC5%jl9}Ws^~w)bsW8c-yo?#xrSYg$*yJU%$*qcFtSGP} zY#Pcqy`>v%J;R}Ku=fj-+^%}cfHRe<-)lofgxQAbhC847ns6Lkc}J#%`#6ImW7XAv zm2Ey`(a03dlD+MaCE~SD9b)?ID~GIII;Di7uTD%C<4gG`AKIny2%;kE0xucqa9>9< zW*;~-L8*P=&@PQf=**SRp8YgwypKL@d>UL2KW(w#8h-k!KlWORz~f>_FivWJq9AWl zgA_45b5g{74) zaXB_mLT=&e}sV)U)Vf0ur%`qVd(dKl|4E5gqjEAJS_FlDqC+*$7ek}X= zdQ(=nuQ&CjJGDQmKllDzC_3dAw~knZH$f!SzvHgwh?u#?<@&C>S`L`ZwaDS(P@_xtJ?ctY)?+O2{)WTtCx$1q-qQ2hN5gQiC5=*VTJ(1m zPm#_&b5Wy&ysvF)^vUU|G*Hw8)S2Iq3q+KUH!2MnL{|gRIwG}{SG0@py<)7U-3eDt z5D3K@n`mH|DbgCYm^0x>_%JE+a)O2pu&gc0%duk9Dph~2@D+vXAC~A;couyz(IDsi$E7-`o_bt@gbILgC$5)G`5`7-f`o zC^(i#K-!*LkI3@7K6Cecovl$y1Pjg6r+Xau4siU!O|R)z6uBP2v^ zuVcJVG=t>ssmtjld5~)~98)6d5Gx&%g7DtiM6;d0ZQ(i&&m#K3d+A zR!)O?#!horCv)3Mf|>M#KlwyfZV*|4ziMeb@wjhU;TB9uuZ3)<@0Co<*MvXZFc1{kgRisXWRZ&O;a?L!P z1N@Pa4|Jv2nZUCos0qRv3f@F?B#Z*T!N_-5d4~SFO2L%^u6V1V-A5>V? zX4lKC?_%jB?#%K3%V_MT_`T76KIGKHwNd?}CjYSdY#J4T)4=fTck--#-x=Z4TMLkr zn5bh-LKeqyjvt0Vq)G64JlAo$fU}p{S)H>JxU&O0v!>aPdr(Cm)sQLmV;o-R-KFrl zJwg1orI@V3rh3sB1fRHe=?A|2fiDNo9Eyz>l0ooX@_3f?t@&{(Q5Ln}X{sujE0nl8 z8bb(YkR^o^YXTS-a>6&rG}}aTSfVKr#yQihN`$776zxxjBt;<^IJ0FlHYaFmh`FtU zgVQ)LQFJN?rj0gpz!BZVfgwE6T^TNxMaMCp&W(6kd<^~)XS}_Z18=Y5z}qX`;|-dB z2k;0X)*r8nP&85iL((?H@touzf*hUF&w=a{kbMA&oDdf5DMSpa`V@!<8RS~vEiVwU z=6}3>#>8;BF9y=?2!T0h4b&JKoq-w!6A`+ouE{(2p~mPQCXk^p&5YTb zL%G(}0|v(kKxd0ZFd~qGZiH?}H;!7M62lpTla@}VI#J6T9JPo{jM7?(weG0wT!zv& zzfGe`4Tw^&wjBWL3IGn934jgV0Bn;1S2pE&~)Fx%a0f8x4mP^tI7StfR$s}T8T6j4J#JqGe(k$SBR7YV*S#y@S ztT)camGx%#R<0pD$=P_cR-t}V6J64K&}5hfhn;FPdz9WwT0FQe~Hl>@{@sWB(1 zV^CuqVdFFcw3^cRbccsc4E`g~9Gv5Y&Xv5T#Im3}QSR*Kh~?CggM-o>R*-_QO0cL4@X-xiUMm4qNK*7mttgcI065X!*LCT zf_b-^E_`*fDnm(j16e#nTWtL779#~xfhydx(V13UGo=I7D-DN1b$NRMi9iY|QeIUM zc+pww)Co<{=ybEs4hqsu6UrJfufU)|gX-<3HHCpo)%bV~{!a$5(-4pt8=V;uOwtm_ z)?s5>dNuTC`G|}v_*X^3zR&-9mTKy#eH(SUS$KH7F-JB+J(&Z!>a?dPOMMdVGAp_)|$&8MjdsdYvHh3})Zz%7~W^)F0k5`;`HfV*-I~#;LNj?>xDSoUZ{aJ4_RC0x|%&)wEO zkH|g{DR;rStz^#S&MvbZg|pl53LiPtSL=6_JA@M^l1L7QKO?a=xuWaE3Oh(q=AmvS zUAM?pM!B>YWO=z`aPe|Gt8zx73HN0EE=>rVic)b^pca&xlUFs^E!ODqIG$nTuL)lBDy*vDU>2o@9;$E9eu-%k-Y2c5=hW~ic?w7wt|qL z_gpas%nf1;BsK<(R-am=O~MQVi(s|jPYq|Zph&|2k%m}-Eux8u^20q(g=ijxi1!0Y zzNQLw1Hwr{w-5kVTQVP^EInXXObo~+3lO_JlPWKfkAhm&WgtqT*4*E>Xb8d#NSTMV zfL{)1|8zk6e@#FSg<5wF(k?(TH4V`0UH`RE)RnGuLO)yii<6`F*7nF7!-II(G4-i#YuK#&0J-&ln2y*DdWoEH7LSA%qCnkLu%Qo_mqmzbx%ox+ zu=uTev1XdBM@$a>K$O7?NFuCwEVX8Y%xKB(ayQ8YPKLRg^vUqzV1^e*p}Q|$$LRNU z8(spU+wLl=M#xdMnXe-~pu2v=j#Mg6>+Q@H560<~da8EN-7<}fz-(=Og{te~RJa!b6R;*Eaw&)7y|JnPFU zI;S9Y3PS3@330FFFh^;~#3IQ-oz7rEr2z z(~Z;HIU}=qGIXK_M4kPH9X$4&(3d`F;?1pZsRq?+`@*<|G>NjkZkx}d5)&fLUYq~O zq3pVE%xxv5;ent(ELR8#_Q|7J5hP|l9XSMvVi@UNCc(JC#MhHC|D?iPAkQlP(i%aC z_~|M60_K})%8+8CzL*&&xVrTV#JnRo<21sGBrCzZ5ag;x>PHS?iOb9N&ThN~IYEsW z97ph|WQV+snrow`m#B`p0R4DS4j-{z<<**hTfER3vg_VA+`YxFQu=O1%uK|ccKw@D zX`h-cDjl-RDRtVtPMvl+mb8F$wo0w%ol5O;N~LzMQ>k4(E}%pSx&eh*XTn#GSJ#@B zY%(qkFh55Kn>mV)L~zgc1mmiBr=gfw_0zk>6^wu)?dyJ41hvYJ6R>>{_PCOwn}KMZ z$>srOJkUq*U}G@%A!DKq%>lWPOjF5)#3q$Ot(psI6EXwdg{*NC7jg)pQ*|!nkle-E z$xPS`$b}rJh0tG3H+jeTkTol9K4gu_0QrzA9b4&6Vdp{)b-9o#g$p^rOqg6oLF9cO zfowCE^o!a{$>7Fj&8GI!pw$>gqI+KmXP4(gnxz};xI2Sg?#^KDB;|8^vKdOi>71ll z-?F*AVrmb?jxk%3Z{1SBfjR^Q)sz7gguF&Gl3`=m?0F_kHp)ONhD4K4Xp>?{6ad=A zAz`jWLsFq-HzX~LQqUiFkBKHGx`%|RAWGYi$cCLdBza*sB$XM51mnMFNGxV0bhm_Y zf;1bO18F~jLQpZ74C6*XZ+=t;ZB(inl~QL^N?oH;xUTuAXf8{Wlsk7_TUogn7J_Fs zEDnaGboeQ-7Xys@Q^Ko&z#uW)qmp--9AM1E5;&t5WKzkJ2Jpf+8kAw;eItu3q0vZ@ zW8$PH--t19VdE7kMg%{If7Ll)RkEZ@SLnKgmAQ<6=T8EF2$9^UmBh&)`}%#7<`{iE zSxRhZw8$r?qHMocFwP}DpO0?20>$R*Z!4w1#yImt-2 z@|uOM5dwxrbCk^^hJ)m4Y}uGDX2ETL`cj1m6Rng^6w{JhQrQ5e zb$T*F?D7~O$3mQw%vm44{_L(5zi0D=3}#3S3%f$nW*?oXQPu{8xXuOy;lL2?W}~0r zp55*Dem@o;foLL#YL=1m4fB52H25Inmk);}l$gJ&Wr72KIA}dju$5*^6Ae4wuOb#K z9#+DuU5&X>8D-c(=h1E~WbrGVrF$?AYgTqP80%~>hCG88qcPY!950z=I4XWP`nrb$ zZ@DuZ3JJL3$gwiSl-dl#k$v-lc`aNHp?`>2tH(Zl!shQIjN<1Aj3Ad9SJHVi(i9-2 z+|r)DmzqO6>w7?Kh1vM!_$?e(HY$5Es=G}^#jasu2ux@txO!U}c67+=KFV|ySsIKzFpoEx{KkBx6Ud6(MCF++L??P#+!MVb96*!i zPDNeDygAMs`aUbO(`s%-hanR+J)p{;i#+3SOar0gs^)4#SM5aF^SKrjT+yfl(O@wU zJK)eha3CdYIO&Tn5ha|^>r%btLK7NPZjI_uKK|({!SDKEgZ`kTkHg2R9O%5wwcS9L zlQQ#AI3l#l8Ef9uFf9Ovp`}#M>pB{Emrkv0ysI+AfUP+&0xX%N^|SQl^!|K}WHMO& zhyJkE{?q5g^J}u0%>cuyZNKfLa7kEdfAXa8x=rBLx&KT|G-QE5l^dI=TOzG+$*8nm>KH# z?A0OVd7NQsr2~!a9{@BpX75T4ntnmk^5Ae+;CRSAG|Cw4hDKeY``lJM#sGs` zHDt1|ktFKg5fd7%^glvcDe6P82zN#+mEkB3tjDlai&r3=KbshiqmVs6k}@}Z6jcLf zb?tvd6Z@FnVmO-}HUQNdD6lZ|fqDf;|AXrX&uCWHC)=(V3Mm_<$KYb8gOmlxL1k&= za3Y8>ze%aNLRm64Jk>NZ;g?2fXs=q!vVz6JR5xH`*JsHd1bl;JkWDzKClYI@0Wc#- z)apPw@XR6b~WwS_pFYmLL(i^YZF^P!V9}3$GD=vM+ zoOVvOxt!a(L~USoS5c1$*Ay?Fx$HH3KgA7U_B2o2thRpG9}VDg`6RHe%uVqRt~DUb z$L>RDFdYsHHek3$BCE8`IAebh{+F?@!ts>C)2HxM3jYh{qbClcCDL9B1_HB9 z91!)ltV$7$OQ*yWp`!zjW89af3OISFOb2@7eioOYWb;z3j&Tnrp*CYwpG?`e2>R75 z2Nr&@l7M<;@AqNv4>*JqcZB{bAl8}n8W+^7OQx*oA+x?k$X&Jo{-?`BME+P{nAvm9 zToVydm{^`R8EAp^9GxM*0iRnVr%4?m9L|_0oGIGCKfsvpOKLM1^Ft~kabLF4mq`q7 z8trI?e%UZ&GMo&Z)2zv?ugR=`c`Y~V#jptXA{AiJ+fM_|ev%LrS(!<#3%cd4r>M(% zqUO_++S$#TGwan8?5tsI$p!4?2CWjO9t3{l$N^_Rk?6=olGxs2bW%3C;gl4-gv~8p z%5_Ke{{FDoykKRqf(Z_S>7+=dBcS5tim)#?;B5;LLdcSBy?>^@nq|$wm-9LJW+nX_ zj2x4FR8<-73lNzh@&G1X5hP7k&LC6Z+qCt^I3fxevi8`*^K_3%G*pIMXJ{P)La#{r zy0&p?l=&A|Dy4u>A99#sOV2@*o<_(_LeFj}=6ITXOyD}D318Z_lsUW>;8c zDJ*umRK_jQDPDOTg;kcqYW0+!NnZ6`}`=+T5 zW13HzYM{_m2Y1JvOjWG|2s1hxAqZhHN342@xmW;dq*qhDq|j6Zg*uvQfP6(C^QgL* zhlD+AAvzI{3KuToQJ!5>B_qVS6!Ab`Nly){UevG*mMV(Q3>J?fA82(m zUV^+s)F*t4xe3wt@U2;yUP^}I*j|Q30rxVjW!A{L^qDm%X2+r!mm)K+WNKnunpsWT zJ@GlSS905uCuFd~u}5^XY%j-BMsaj!Tq>pxVHn3MoUf4a6x*`HvCJFwaBP2XY)%|Y z%0ahnnMdZlwL(4wlRdq*rMD}^v08c9tP*I*Io4n_325PbZ%_Mp9Wp6au#M?7AHIME zwbhqYxL9*78p%?cm&hh_+gZgdP0*z8S`tiH@P`qzO~35t)nY?0@xPv~X%?{h^2AiiIK=ip`FFJd>qVrFjK4I)*uQ%*1i2RYneR&uY z8;Cf!5O2%ZlyI&5ZA>5~4&rbrn1FL;kpxN|vpRwnDAkHKxE!WDy9AwJi&m0B%TtsX z$1GH$iKUzXj@B3T8S5y;rD-8cpMcXHxb!VExlROD0UjnZ^W0o9)&|k7Sbfp_??ggg!Rt zU>J0|%|@C*w&iM6aN#wno`nPON1|AE&#PPorg+bV|!Bjd0Y)swOR};Z}Fc zYr9*1M9Z3vqn^b%k!`bfnc6;>g4k$9J?QwoW5cbBE70-l zW8)lH#;}nuCE7J?u-oM9#PNH;hWt8_3=>AviR0JDhI!wj4VXoPNII0&wLh`fn@-72N&EjM6Qw!{4Rl>+f%v!!UsOFG{ z7`07YBvkx-5rvOtMuEcy7j!r5vIYr-NS;H>gqm%+K?jDFq-Vkf(Uh+#Qsj(Pn5-$% zD*9GF(M3$JqZO&moMG%5aWz38%T|)B;zeMB_B)sWQILmgp%tlUAH!tVc1cS;AOD1-DK7n;6F)p}h~3EIO(=0cnsyOV`#a-%|Zk`P0Sj=~(0NLB(HcO8+QD zsBgKPSl?k%d9;dtvw%I6!#3V&c@xJ0Jl-MmHv45GYD`Iu$xiy-zKfXxpoVI`owyk? z^in+;%(j1T)iUM)ML-yyRTtEZ=%Xbo;3vAE>J`m8-Oq-;zvujcWH)>~w$j8N+6@y5 zzg3UY?>FkDWR9xz93PS^QL24m+0^vJ|w%PsOd)DV8Ro zXei+-cTpJ+##TeaY_G8_sIMW_&dw@p0$KIk3)WEKh}8dx86GV3SHKv3Zw_I|6kNZS zQ^7QNf9AwvTrS>}F0p})Eb$S0gGKmBv{7T`*CuO{Mff+v$in`HTN(9{J)!xOC|hu( zwo+FHxOi&|TDp|}3fhQw!_rpaSpGVQ#mdUQ@ZhqF)=>G2m2(C#K$=YDFyg=fEoQYJ z97;+72F0N3oV;5j0*xCrRV?)2Dh57Py&?!;AH`0f_l<}bZh8c2w+H|rM}lD{38ffU zM#cPFl76meYRDRRK$Mc3a!{U_>he+@0j2C;oluI}Dhc_^{D=^=(=<|LOS6*w;s?7g z!Y_4%0Ia$>fj8{d$qlkfZeGiF`C#ZaDpJ1j6w0I(0?4WaJkSv}f`JFA)I=)6Iowwf zy_6ii*nP_9JtXY|_RzY*K_*3zWTf$MvYNNq&)E*+fh62WGT7p5b)X{dFdkI$7b>eE zDSLs#IPSi^z{%>M=E`#z545_`8mO;^7_B^q@nAGBKf0P7->iNPP;Xw|$hc)MSiU1u zkPNM25)rJR4r3Dg4)Z!Ac-n;Pus>(kV)=cuYwzk)i294k3F=6Z)5 z6diP)?o)G*S%;~)K)-baPJn!fViNGi>UKdkS7wsUc5_!`b7i`0R;G|-Gxo$1R0WX`5bWQ>24 z$QouRkde((ByygM-uxX(hD{9lsD;IKy`b6t$){|t$&9(E+M1aU4HO(L6MMBF=x zEQ#bLvI#~+u9L`!YEC4P?F9}_BJ+Zm$X33lOG#uaZ^UTjEoFb#1gxe!6^wWy64p0_ zgw5sZuP;_NBFKMpxw=bYHOpRi>;j8$+GbN&EYUhQpRCZPWWJA=$-LoFz@moXzL;;S z@nsNq2P-za?$~+lO+O45?`E@}<8U-$9J?Rir2V!ZhRy%=NktH(7`QdsWWqYs*;uCm zDKjnLtKVd?hP)V_g)26;DYwEj!S7cH2Gxhd?2KbKxV9^raqVuqW^I>!+wI5B4d=PN z9bIFj;@y;;^z220J|al9?Vpyy<{s`^y~Q*yHrKWf&eX4MKdCR&7I$~Q#(f=TLHnJ3 z;e2jve9fOMv^PgUj0-sOK0;17EQ&A6mcF)5>4RRYkDa{vwcXz*8C6@%VXDXOEuK^A z>euR9CYEdEe~9qp z21A5Hl5*BZT2VR9jy->Fw&e81d~^g5{A$2&UB81^ENfpsteOzne6)kQEKZz~A_M@w z-tf9)u+v(0-Rqi9o#lmPL2oZOxCpXDVjtwAL_d?t@{+UVZw=wLxf=@$9Z0%M=azIn#{@cr3; zoY9yCLtb)t3^}Li_l_ZjraXoWvE3MgOlQUrtmF8ygCP|yLKM|I2Eu`Scfp5)?Dehr z+4tW3f;9wcT_~sifetrz=w^eW>@8C9tzmtb!trLddUEg9QS2cY1um`_;9Y40K$-e{ zUI*8%G$WHT`!(3hqtiH4a9A-3g|2#`R#!89)k}*egS5k1ZsZRW8V+kzvXL|QUAX~> zm_%_stbbP|CW>DXtn&+Uttjy5UfL*U7hF7d@GQq)B_zdl+vY9rx6LIAByCqSYU5}( zpRvJh5M$$XOm^g1dv@Nxxt%Ns=S8(jHs{Q}cM~j%t#vp$d&gOOXYKaTT)z1=jfrWx zYK{ef!KO*aIlN5759OEJ|RF!SQ4xy32ugmcpjAPYt#3QtfbRuc`@Tj z6&q_6Dh%`D8K#CAMI+s_t%K)hYCl+!_QY#;+zbK4?95*(~-6%As z=oVInX`>HAVSzzm>EV1juX@37IGpXV_3USvDyd&3Tt*1v&Y#ezTsv5gY$%&CrUr_8 z_S0cN>9+i@kg+_Ia@@ZAz3lO^A~<%q&o-ZvJQfEj_U1TK+23sA;YYKVc^Wl7l?8vW zzAfBh+S3>7$WI$|6?4Fl6M4OZ-QXnx$vPpv`v$(AYqhzvQf(mnt8?bHUhg=oQvZyv zhrIe;_-NfhNF6i6XrnVuh?y_gN?{Ka3Y#;pusQRJl(35i{T34eceUWY2Rh&$R>%Nf z8=xG{CWtQFV}W-M!g+;6@NkQ`of%LsZ%~!xqHA$D0N)J2ySEhg%moUa-Ng}ski8%zgjkZ= zXWfpZg9!<&Hl-!iIItr@g#$*%C}}t@e>lIDJ_wArOJ$IOGNafX8X`x&u%^Ug zh1eBgd$DV0-7>?7XPV5wxp&gpt8Yzzp_ry};LV-_i(TUjp30xFa|dP6jOXQ)<)4@s zF>&1`b#gi5=oA{xsX>z>7L_(?Rbbh+{v?~~4S%~wtMEveL&us`D7(*Oz9ln_K;qpy zrji$@9QH0!nGL10m|xujUwMr(FNH2ys9QCpNNo+R#QW)7PB&aTGC#LbxB2%(9L`j= z5#J@5zjg+_X{2J2uoXA%5WYJBr2&hEwH8Pa(V_%oG0i@96{s4850cn%~bgw zVyCOZ2XA$CcWQ43Tf5@wkzJ+kvL%Fry#8IaDcD$?r^C@m&ojLAJj2Z}5)xKYx1K)N zHq-6FO=5Bj@x@|n5&$K1$x~4WNf~n$RP76UgJc{ zC#G(nkT&@Qg|*WGT*N6(>G27zr1^w|&3b)8Pb`Ja;-uezGgR?H+5EZSyPVsc^n?B+ z{X8mJnV_UNP4n#C1C=vi()o^tpKvNB&04J-Gy^J) zvU^b}taqHpuV$Zm^I3<Q0uKnmuiDEIZ+R z)(v!tt3VM16ITJ_DejH^I8HHrLOdFXx5tFK)X9Z@FBep#fb+VXZ4c9$8DlAD_5=qQ zj4HYei-^f7=C*9%kZt&x%ba%=GDptfOYz7CyQ?p(zrz{=OB^eIP??W~QB{>#kc;We zR(uYFo*5}KzkEN7hGbUFdl`C^ui$oQPK}bJ?yX%rGi%q*ytRw42Lug}x2+VdlaPdZ zMa+TUY*RaF%bM!Xc3ik%@d>aeir!mGSWs}Lx0_DfiG`$?&KI18G_zw`tya4%B;;nt zLUIT(cL`0^>zLa3O|y^+|FzpfD!$TI5P{=&WFQo1)=n3}ir+<|j^81P+}Z&f$Y43A zSSs9w#XHMJX?ATRzwr?w@{Ltc_qnNCnR(^)d&i&C7-1b1QmBTK(xjOYl`>> zGP0+Lhv8}`eIjps@K$#I+x9)G*KAoG-EMm$?<#P&8cGes3om8UUh`_lsl=cXmTr@| zV%sCI=w9<&GXi(bRN&;D2*n)C+F}hAHUfCo#$K{oW~%0}sV>y8yqul&g{X`W?NR;h z>_?X_Jd~>*9CjOAQyzKrZtvJ%>n@x3)Es`f(B8R~{y|$mj>k8I!g{aC&g&7vj8*`?>62T}tv%FF}V?#}w zl<)1PfjF2ZxtrW;`4wHfIirh{ zJUw1SYoe6E!mCCaI&G#~RLNTVB=xGj=w` zXU>ES=9Lrm&l?w6ZUBB%Za6EOqF-}9Cvw1>ekq4A`^meW6uvXtoT?7LUE z!)SKVyXPIRz>ER2nZv_@UvNeLJuJl>w}U0@z#`*a@6ulzBThSkxBvej_VSq#`?B8= zV!skYu}c{I$$;U?KJw0u|F4s-btwAanOO%_#M z^uZlKl8#f=t-Vzd%1mqixZ`5GQ{ zHQb&pJLcs19wq+Kw*1j`$1KbCc<4y7lYZ?lfmYmHNA;9;^4V~H_L(0a-TIj1Gkqzt zygnQ80Z(ap)AVG=ov0KtBtH9)H(0Sd9=((Z7>CU)r{Qoa5i(>c|0^{eA$f$p#Fl77 z0!t|8Rc#%{eCl!~%3IRgAlh1q<<&I1;wKIE^_RIeNJbhrm5)xkJef0EN|BSqmDg$@ zu!fT=KgSkv^zA+OpRFy(nZuKD&Z=TM)htsrArhyhS=z$tR?F(pCQk_6gq8ZO`YqxF z9-?$|tY2p&c)cT>1Y18J>z)U{DuuONYIOEWB3@JS_(M+;Jd;ri>X>4_NiBgMR_5eM zfP6JhtJ`f=qP~mDBYhWD`+%W!9N;>`*i|WNR4ERWB2d@)(`cNi{3<3MHCUFU{!&8x zHdsuHXs|S~Yg-`)j2OXejLjB1-g3cePnM%#fR`ykb8fT<6|v}aI3GL`qHX9>5Hoq2 zRt^lakcM$hhBSu5ZVcUqWW#O@u`Bc4)wD!9a_R2c@R8xs2Vd6GJ8c9+K0MF>OfuL3 zroRW6z5)(qwr9_LWcOA+|Nn2sVn7g`D#{Ve?qhhiIohy+2x$`TqFv$9!(sb#bso0~@L6E#Kw|LWf|u@i9G4 zU}xpSL6wS@+SMiws#Z1*XSSjK3Dpb3C}eeZ`>of9d+imi(-SJWS(S*U{W|k=bv@C( za425-m(3_W|L=EjiBMq?R+Qf8x^tyxr8q6t8T{@1Xt&PN@174%3O@EK&HqIF%S}w^ zPPX}9=YjV=c9bjl(hq;B7=B|AhM9r)63VBZ7#nyAre*fBF6k8ZRs{c1W-{oD9RZo_}z#uRDEili7Q}F8`P&0qI2dHLX^2yOYotE#)QAS!=q- zuw#WUS3lBfi@HI6h2JlYH*2i&wq5xKk|AS5;B)y&+;`;DN*{5G!HX!toS2~2EJ=q5>3~=1qyy5a<;6qEbELdz zF_uk^xXskG@!gPp)QUQN9tmG;u!>Y~6A=uattsD6bcL(WsRS+6k^n7)jcwCZu76UW zS_~xUh8mGM@UCfATxvV&0ngI}6-?@FW=U43BQ6c3F+!)xd8Z_0SbvuHsY_qwx@ig^ zgKWZKVsAuZ&#Nx?abd5zVCZVTmy2BeTw!*@g;ua*o#0;^JchqqcFx21j`C!@tAd zBp^Y{;vJ$7Q#jm9g_-I|A$3N$sOn}XwWIWk_jsA%s(g#Hl0GNvgtQd4b0>XADi7sA zhZ@5yveSZD?Ho%iZlJy8Q{lK4h#?o&vsn&AH@w7)1;ubMx&ROt&ViYE1p-WGOk;7%et&7)bb{hhK9_yp2&gE0l$>PZU+Y^xW|*>npVsf7Am?eWG+k-cWnG%8AEzwKwo)^ zRhlzWi`a%hXQUhHEzX!g+du>x(Zc+lmOht-!ZA{=xMCR++qP*&o3gXi$%jQ|uJm!= zn(ZaH$eNeXRx*RWaGWpLPVcoFi#s*tZH=O+viud2LePR%7Z9vDh?ad}4DzCYFpV z33E*;%NQiwVk*PRoZ95`<|R|jw6~cJbem;_8WNDpm$JDJ%X~+=L?wIGJ6v=`<*>KD zrix0v%626ag|w_nTAJ-R;#C}*)bn(REl>P{Z*5>rxIZX|x8{(pR6fMV=Q8cE)gNbA9eHT#DCn z#&9Wy%h`zL=nLJ`TgdIZFiAwKlF&wy@%a^h|ln9ONx?aNcr+nh3Bx`jdcl(~-R8 zz=%e^GK5(66sHX*oh7O*&LO@gyAA#|dICz3{vRIb58XdDP)=H9XOlRSMLVGLhTLh9 zqI0JQJ&rr>v{t##qg0|I zI%-kS(-6hyADS|$k3MX zN%&#bK}j8#i4Wy*_ul@JdgoY%f;{|b9@S01^WmGIv-{>C|+1`uYy-IqL zQ+}LguiqiUw=U7rJLgx8Pe2)pWM9qy$bUK6niwq=`A;Cj5xX7PU>gc8~`ku&jH{HLz?vm zvhzNru>L1LWnulF<%b2?p*xoBwe1=qHbsJ__Zp?9QT@w#=kE%#pMCNbze%%~&)n?1 zOFGRylsEg8EWGKc@V4xQH?KS3{rU5t{?8&p`Tumh;tlQe8uy%JC{$eiE7=!sUdx;P zw>%}==jJuj>X2PEV>R1dH3+o&okblN?rMj9z`DTm9k=Yi;s-fXp~3;pi|#PJFYi#8 zKI%F=Q2&=~pU)f^zAHQKGZW!^vYS8i%`|Ow!1)!~ZT!AB3qSW9 z`yI_*`MGC=@8)_m`vhn2yWy9gJ1L~c^}F`U{`R)r20!GxRKGQQ-))WXe`mMewx2yd zjNiB4@$^;~>QEV9^4L{Ia)I3&T^;DS6mI(ROhtBdVQ!}9w-nEX{M+*)#5(@{^NWtT z!lImT<45$s$3zjrSUtpY(%#3)90EfP2Lu0G zjaad}Zfkbq;>lgOU=Pq77r3b%T-@sF>T@lAG5g%@d$)Sw+Zp(n`8oIxd%$|Z?<(*g zuE0QV0WAHzhQ}zd${5xt{~?b7^jKbEtHUicE!M~j#8XhT&6Vn#f}%KcC_46zXF75@ z;2sAN-%qID?MG3Xs-TrF{ICG~&TR0`y$^U|_z9=sFB8WZdaH5N3DYzQ?j%hBBLb_w&HUH@>u2_Qt#G;jQh>M}|w_tWVy(H@6@C z=R^5z{L|ArLM!&b^h-B(fGMagc0%jCe_CzT9>H(zpa0suLuVV0rc7I_OCoJY|~uPx^z;r!`Eb=_?PEB`vNxvq%dQ+)&I$( zO%k}+X)`N|)bE(-`Nf{VsDkvPyRw(vqt0G-&z7Td-Ktovj~!#X+~~^hZ=(nJ_vY&D zA*bH_ry)`%ob#n13(Qs~-e0@>%``dRoYvh6L*%4pg`zi8wok5RzYBtR{wzWKN zcQ?Xxl|7Si*IPQ-JH9@?`?b!h5mxJ0b-}#k4cYx)pI7cGI`qZvqS=A>KFL3QbN2Fk zpFZ?s=NE|Z>sMvh-MjbDnQt%<{hbHyU0Qii4OGt!+ne`i(}gA9Sh%1AphSfL90R~@ z*>k?}l<=zT4c~ZE^wsUz{QLIY{hQWbEQ1eer1MTRgY?bWpWZjN`vvMKyRu@osXbM+ z61N(AE}Oh>EW9v#>wU@iWmkBB&?vsIn)9)(2nh7L<qZr$2dz zd$QZ~C);PPZG19FKR)YAcXS%czH-cye!tfB8=w8c=*%;-A3Xcg1?PJs+ZLvpYety1 z{%CgTbDkW1;>_&M=j^_lNidsNOUKQ_x+(j`a~5^B{(zcmZtD-ulvV^?-|&>@t_+LH zEs0Ri!~#_Qju*|(&i}Iu7hK>Q_sDggC z+wuH`Q=h@(gg$xp@bi~VeTLX#mvo-3JZ{>vwR(0a&o&?T9b$ZTruV8q&?lrQ;94VuPKF2X@!V%2Krqu*c83(tZd}?Q=;>? zW?PTnd#@?)n@Fm;w|V6T8=?D-A5zbMeEc=x+q295;*tWShm;el1V#P6M<6}(g-fFI z&&oExaQ>fgW8-oIM!|Kkh)G<+-@c+rc(UuCa+(c+>d z-}&>cR`Kt>c-6eej1nFbDfGFu zi+%N9We2=ukEtzgP>V+!`17;Zyku*5L-wnee0R)s1QnwX+)<(+$Y1>B2d6zOxM#;p z_nY>RIvn3dFIzJ0VZm2Vd)bnsrncEV^vW*H$88l*5h%__tpjAiQSX1*yH}hidQ$)} zP6<>e9Bjur*NEnd6%U2x5fSwMw3n}*?|43cM3lmbgj-&IX>{J1*^6H>Z%PTG-j0FK zeZ>;=^!8V*ue`Gsm-%2wStiQ-?BMHm%bxcB4L4kJU4Ixoa8>r^s}IS(di{Kz{_Oe_ zngbo)V?MNC*x@1k5N^P**=uk3;U6h$B20!h5^l1O@Ri2{COe~b(Yo9hyJ`n&pSyg@ z_pvh{W$VhyY2<{Do#w7iZr0jAy*S+CsrnL#y^9DK>3P>Zc-+?)wqJNjIPhnDh?q?S ziCi*$MextQ6*A18{z_Q2eOk%>HG_@Kx)miPU`=0!>*qLw1!U_BDef%Y>)x^6gKn|ku? z6F!|X!&kZnEwa!Efty*-SdfRPCgoo>nPfeuS<%Ysfd?V|G?qXINa%oKX5nF;)64#qwlxIziu?hk|8^6K?5gDRivRTd{kle6Z?m8r@It73cmywh4T%(qXMW8z^@ zYm)$3QVpi;Ef2@OOc?sFr)g89y|y&4GdX$DGbvl=3x|J{jGSyXHJ?4SC88tx>>=;C z&mOW9KBYG+X?KM3`cAJ5@VKXNcHoCbH|VzeTpHitb0E0SrKtz}U&lZn-Y7RKrf)!P z+Z^ZlBM%gQR<`}>v8N0E+}k2SqVTp@7!Z=d=X@$a zne<)$Vu;A-1b8%WMNX)<-}z>=zIjmge%j%Ux|!N4Le`*(Ouo3Wt0O{1=>mqtA=!q+ zus;lv+kdes+=Jv0L;Wy)o{IWmKTht$btCC+r>2vW4+mS^0L?fO9WT;n;NgkYL|&fu z>PA17|H$+g9(uOVwe@cI?g=`NtQ+*sBfGNoI*;Yw$a%Die;wzsXKc-RBnec83fyMS zV-x1Ga5E{J#7Hdl1&>vGw3L*-#cWY_>GlJ{e;0EP6qJ!Y{E8$x>&$Go6JMfu;j2$v zHIl~*W3ePtP9G&RkEp}XPP{A4BUz&s{^<_II2Mdj7F-kXN8|PuHD6kB+6e!j2;cIF zfA?V@eIA!+5*Z+Ty2eFLp1SeC=*?$ln>M~|S{a-=7dQIB#^%@u6podzUsB*Eq*)@E zb>3XE>BHe0v#)JBV9Z5tI&JV(UKhVa8{Q7)!fhuzC8QbU!4dfOE>3`kQ?nQ z#AtCrrj48T^N@5Na^|}E!OaI{*MEF8d@%dm$BzvEGaLED-r=F`10M_*cH-GYR(5eK z>wY)6mo0v11ChqxcwN_n?PshH7qnki4p)U=Xuqu-9y;eyn+}9nmP;=-#mnu)N}$&If4D-7=TZx7RKKc)lI<}kRl zlfKP`MVpPH&2iTzp-F4<+lG{ulX((vT64x0P;mt96NYke1Qz>VWJvGAzFrhye0AB+ zCCygKHuSNbe9W?Rd-}x6qzx17PMX+2UA#SQgnb)qrl2L@FsK&1MH=yZFn8n&H>=+% z@s0B7qiI8@C=tWRdJ;7*5k?Tmjb``KoSv~nWlbB)#Elzc*>Go!r5{zsawVX_r%{U$ z?HQ_l!p8G7u&wj6zC8wH+cHm^e-Ny6iN|ELnr8|7MTH|<$Q0b7+efthY z25T*w#SmSlcNS@$Tf5Udm1Bx?yN<@uHp-AE&C_fhJ}GMAjaYbRNQ@*76XC81)x$gHAMZbM*eKq;Ki9=n1;j9sMgo*7BO z%paefr(GAB{A)qcfCy|W0yYyDhP8}E(f|fu$;KiQoCf-`;J^a5rxviSGuW&Huu*@` zZ&NVZ!*3N89PaDy?=J-{@ep#Mhv61$Y5@!iu{}E_(gEFM7LCJV?Rvs0Cwc&rTk)k0 z^7VM-{-OsPSURO1@Ywd=)#GKsQAI~u#}+*~ZVHyaCA#t0)nOe~(Vi{I-O!N0Wep|i zWMc4u!=RAH;vUED7=zlc&eS8K!0rBP-}=OM>}>w~r$XE6nBiRncgnnJZxy zxsX(q_OIrLeJalx{Y$$xy13ksDC$Sg;f%4<56oRl)x^xCVbW+HqdKsn`;D_n80F# zM)qP5m;g)0$(fuiO1a7_O;>q-H&d5D?3P{Ie2-UpaTS-k(0W(TEb1_(y5!+3g+#So zyUn6?* z%}l{vBGr^NE=NuVgJR&Kv3`HxwuzM8_e7nULUc<9XuE6|^O(s`-ib#8m%C+*SaIKx zV2{?xI^R(E$w#we48ouj@Hnd5PZl687xn08R|fFnY>uzUPL!tl@(o!Z0 z1t%fB#kc8LptsCOM1~TY9go(oj#}dgapJEl-RH468+V_~<}6W6*6fuo+a2-WZMNuJ z+_RYCsvutLo}vddmgoW2(kJe0d`-MAVFnV-#xm>KH3Iu4+75^e@lKuyL zo!RKb+LMpce6l?61Q@?@Y9}@~qs~Uy+NRT%>#Bpce~In)5TyZbHvGi6jz^~{WOk(o z={+HvI|=g3J*;o>4+OjJpATl}G4GwOc%Sv>YG39ue>_)Z!L!>9Pyq|qxW4PC24R#u z?4v5wv5waqw+v9lBYfFToJxgPQdW!Y^Vg6V)|kz|xxs{PSwU*p0=MTn6sfl36vki) zZLAN+u(ovgxEUFlm2oYALcFf>{({BN_R21@I+CI+Gr1W-T_ z9eUut(SxVuf*zoD(Sz-gK^db5${0OxoP{1hCY6gG3L8#D+3NFO9~91=IqwT~2*Kqt zq_}<_!L>EPo5O^QWw7y@fO=K)=<1;D?`GVsSae^gHNW_LPEmlx_h9N323~}tnP+2ZPgiVwU0ktA+)W6hz#Zruc^S8Dy zSQ@S@U0#a%+wa;&AmXb_p}sd=)1I?;ctCW)HSP6#hfhw5#loKz|JcTD&g6zT#lG!} z_70DG{!;n~{H8J_?(E9PF)B@-n4_V|7Fw=>gV4<|@b{D$?F?+2eb$h(V?Z4V5 z%)iD;A2qpNzp4F`aa+cH?LOg>r_a^EEejZT&@9zlSraUB_eR$Q3Z8LeiMz2F|4quH z*fH>f?fSmq^P@|zX}@dVaAEtbeZxJXciY92l47;;Z2kV}zW?|7J-?kS3y0F3cYbj8 z&cA~>9~<{)YfQ#8BL;ePP8e8+r2b=XtXUL5sw{V;O>$tuK>?DALHa9rNOJ;MD|nni zdpmQBzki~5S+QORNLBP_?ZFkZK=GC-P~7GE z{+{1$-`_T+)qirW-txO`)kUj1<9w%UH4jStj>g$VVLPqf;l{Zq+NyC@#v&BB>*Kt~ zbYhc?M^&n>c6dNIFM4;>Ubj9RT_Kh?wns9Qn%H~DaaqZ-3p5vwcvul=R#&EGyE-r2 z%X~&aiq=7OE-nUl?3M5A&=VAkRcN@cR4VxyOPHwli>Qbv$0}XgAC%~(M3*s?0%BO< zx?%&v!zGX`qBLpewab+iAzBeeJ43J7O?-?Rdu_;i4m5b_Rn}S{7qD$|U6s z%TXpNXIPGb8&9C>OV7}5P*D`1WBbPgW|MDv;{*MtB>nB5J~^DXwEqBZ{oq!KdLWa> zzy!T4c3C%A&SnO@>Yy-*{+puf@F$2aO_qc+BWoYS-%oD(hpV&$*G|y&(0@ZK}x~s}! z>|Z`L+`TrPf$#qBNs&*Kf&Zq5f&cZv+paQc_oPF^`9nrnKX~xsAK9&A5B;BnrxTwb zMfKw8dq2F}hzB=89Q^aoho_tjqdpP(y63+qeLW$G=k=-MM9D89>)Ss!^(kEC67X9O zax0f}jy=Eo(8d2MvID~yQ|Z42AQ)S$(XOW^J-K~m8ZLSoCPM7YErPWO z#UwCu!Y6B4?yj?ZFe0W|VSHI_^m%65#1c}-=l)Iwq<#BU;q$7N-HzF<(OGeIc*u~gxHU$Z zsQ;q<+K+_0Ta@kUaBVS~I8Ijno~y}p{zZG}gW>KKKbXf~a%;Fxcw76^4~47B)t|*t zwG~}_Z#y{XZyO39(yFI<^PAh_W-Q2So+4#?z8C$oIt1vaFWnTAcc^G ze!xiYh+qo=LJd8j*g^?CbaW6=P(i7J3I-4`2r4QFTpKDX_KtAXYnT6TcAw|uBm}Sb z-uHdK|Mz}Imp%JzpPikVo!y<~<%3>mu+3)Zh7}AOEB4$h8R0cjj>a7}-ZNog-#j%B z8=QQ)4tLmDU|-d3e}4NYlL|-Kd4&`XuQFaehKDm>$cmfOU+_A+Hjr3rNzM6qv!fDr zhP*jqfZa2G_yPBk0f~olM0*&HfFh3%kYO_DQ|c&c_e@g5M3yX^MLmXzYn*QXyTvRp z#RMAY(U!r&6L)}H%Tx?x7nG?cZ|}Km^=ApzM`+%cgT+1K0L>i&_`g8=hlt4tC0r-^ z@KD`xop@fnTAX#g=pw`qwB!bHRJ>o@|3)!Hh*xR*O=4hhp*d)~or1H)-Qs;(pDi}S z>pD~{f%p1QF_tpE5QTO18V!T(Q#PIn79q~QY!bbDPJ9¬dh~>BaZONLZvy{y-!| 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 delta 93652 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 diff --git a/assets/recorder.wit b/assets/recorder.wit index d23de90..2105112 100644 --- a/assets/recorder.wit +++ b/assets/recorder.wit @@ -48,18 +48,26 @@ interface %record { use types.{line}; // Records a line, in the order the proxy observes it. %record: func(line: line); + // Reports a panic of a generated component, which can't print its own, and traps. + fail: func(message: string); } interface replay { - use types.{line, line-key}; + use types.{line, line-key, label}; // The next line `start` performs, in trace order: an export call, or the host's drop of a // resource of the guest. `none` once there are none. next-host-line: func() -> option; // The next host line about `key`, e.g. the `return` of a STARTED call, which its mock waits // for. take-host-line: func(key: line-key) -> option; + // Pairs the trace's label of a stream or future replay makes with replay's, of the end it + // keeps; the trace's first appears in a host line, which `check` doesn't pair. + pair: func(trace: label, replay: label); // Checks that replay reached every line, once it is done; traps on the first it didn't. finish: func(); + // Reports a panic of a generated component, e.g. a divergence a job found, which it can't + // print itself, and traps. + fail: func(message: string); // Checks a guest line against the trace, and returns the host lines that answer it at once, // e.g. the `return` of an import call. Traps on a mismatch, with the expected and the actual // line. diff --git a/components/recorder/src/lib.rs b/components/recorder/src/lib.rs index 28bd52f..6336ae7 100644 --- a/components/recorder/src/lib.rs +++ b/components/recorder/src/lib.rs @@ -13,14 +13,23 @@ mod bindings { } use std::cell::RefCell; -use trace::{Line, LineKey, Replay, Trace}; +use trace::{Label, Labels, Line, LineKey, Replay, Trace}; + +thread_local! { + // The bindings of the labels recorded so far, which each line's are named apart by. + static LABELS: RefCell = RefCell::new(Labels::default()); +} struct Component; impl bindings::exports::proxy::recorder::record::Guest for Component { - // Prints the stored form of the line, so that stdout is the trace. + // Prints the stored form of the line, so that stdout is the trace, also of a run that traps. fn record(line: Line) { + let line = LABELS.with_borrow_mut(|labels| labels.label(line)); println!("{}", line.to_json()); } + fn fail(message: String) { + panic!("{message}"); + } } thread_local! { @@ -51,6 +60,12 @@ impl bindings::exports::proxy::recorder::replay::Guest for Component { println!("{line}"); Some(line) } + fn pair(trace: Label, replay: Label) { + with_replay(|replay_| replay_.pair(&trace, &replay)); + } + fn fail(message: String) { + panic!("{message}"); + } fn finish() { with_replay(|replay| replay.finish()); } diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 29ed4f2..56fca3a 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -12,6 +12,7 @@ use std::fmt; use serde::{Deserialize, Serialize, de::DeserializeOwned}; mod renaming; +pub use renaming::Labels; use renaming::Renaming; mod bindings { @@ -35,6 +36,15 @@ pub fn convert(value: T) -> U { } impl Event { + /// The stream, future or resource the event is about, for the events of a handle. + pub fn handle(&self) -> Option<&Label> { + match self { + Event::Write(chunk) | Event::Read(chunk) => Some(&chunk.stream), + Event::Value(value) => Some(&value.future), + Event::Drop(label) => Some(label), + _ => None, + } + } /// The task the event is about, for the events of a call. pub fn task(&self) -> Option { match self { @@ -59,7 +69,10 @@ impl Line { self.event, Event::Started(_) | Event::Return(_) | Event::Cancelled(_) ) - && self.event.task().is_some_and(|id| id.table == Table::Imports) + && self + .event + .task() + .is_some_and(|id| id.table == Table::Imports) } /// Whether the line is one the host performs from the exports component, which replay's /// `start` performs: an export call, or the host's drop of a resource of the guest. The @@ -69,7 +82,12 @@ impl Line { self.by == Party::Host && match &self.event { Event::Call(_) => true, - Event::Drop(label) => label.table == Table::Exports, + // Not a stream's or future's end, which the job that keeps it drops. + Event::Drop(label) => { + label.table == Table::Exports + && !label.name.starts_with("#stream-") + && !label.name.starts_with("#future-") + } _ => false, } } @@ -77,12 +95,7 @@ impl Line { pub fn is_about(&self, key: &LineKey) -> bool { match (key, &self.event) { (LineKey::Task(task), event) => event.task() == Some(*task), - (LineKey::Handle(label), Event::Write(chunk) | Event::Read(chunk)) => { - &chunk.stream == label - } - (LineKey::Handle(label), Event::Value(value)) => &value.future == label, - (LineKey::Handle(label), Event::Drop(handle)) => handle == label, - _ => false, + (LineKey::Handle(label), event) => event.handle() == Some(label), } } /// The exit status of a call to `wasi:cli/exit`, which ends the run. @@ -192,10 +205,11 @@ pub struct Replay { impl Replay { pub fn new(trace: Trace) -> Self { let done = vec![false; trace.0.len()]; + let renaming = Renaming::new(&trace.0); Replay { lines: trace.0, done, - renaming: Renaming::default(), + renaming, } } fn pending(&self) -> impl Iterator { @@ -215,17 +229,30 @@ impl Replay { /// which the import's mock waits for, or a copy of a stream, which a job performs. pub fn take_host_line(&mut self, key: &LineKey) -> Option { let key = match key { - LineKey::Handle(label) => LineKey::Handle(self.renaming.to_trace_label(label)), + LineKey::Handle(label) => LineKey::Handle(self.renaming.to_trace(label)?), key => key.clone(), }; self.take(|line| line.by == Party::Host && line.is_about(&key)) } fn take(&mut self, matches: impl Fn(&Line) -> bool) -> Option { - let (idx, line) = self.pending().find(|(_, line)| matches(line))?; - let line = self.renaming.to_replay(line); - self.renaming.end(&self.lines[idx]); + let idx = self.pending().find(|(_, line)| matches(line))?.0; + Some(self.hand(idx)) + } + /// Hands line `idx` to replay, in its labels. + fn hand(&mut self, idx: usize) -> Line { self.done[idx] = true; - Some(line) + self.renaming.to_replay(&self.lines[idx]) + } + /// Pairs the trace's label of a stream or future replay makes, from the value of the host + /// line that hands it on, with its own, of the end it keeps, e.g. the writer of a stream a + /// mock returns: the trace's label first appears in a host line, which `check` doesn't pair. + /// + /// Panics if either is paired with another, or no line hands it on. + pub fn pair(&mut self, trace: &Label, replay: &Label) { + assert!( + self.renaming.pair_next(trace, replay), + "replay's {replay} can't be the one the trace's {trace} hands on" + ); } /// Checks that replay reached every line, once it is done: all but the host's drops of its /// own resources, which replay has nothing to do for. @@ -247,27 +274,33 @@ impl Replay { /// Panics on a mismatch, with the expected and the actual line. pub fn check(&mut self, actual: &Line) -> Vec { assert_eq!(actual.by, Party::Guest, "`{actual}` isn't a guest line"); - let task = actual.event.task(); - let key = match &actual.event { - Event::Write(chunk) | Event::Read(chunk) => Some(&chunk.stream), - Event::Value(value) => Some(&value.future), - Event::Drop(label) => Some(label), - _ => None, - } - .map(|label| LineKey::Handle(self.renaming.to_trace_label(label))); - let found = self.pending().find(|(_, line)| match (task, &key) { + let (task, handle) = (actual.event.task(), actual.event.handle()); + // The trace's label of the handle, if replay's is paired, and otherwise a binding of + // replay's number, e.g. a mock's, which carries the trace's. A pair whose trace label + // ended, in a line replay has done, is of another handle, which had the number before. + let paired = handle.and_then(|label| { + let trace = self.renaming.to_trace(label)?; + let live = self.renaming.end_of(&trace).is_none_or(|end| !self.done[end]); + live.then_some(trace) + }); + let found = self.pending().find(|(_, line)| match (task, handle) { (Some(task), _) => line.event.task() == Some(task), - (None, Some(key)) => line.by == Party::Guest && line.is_about(key), + (None, Some(replay)) => { + line.by == Party::Guest + && line.event.handle().is_some_and(|trace| match &paired { + Some(paired) => trace == paired, + None => Renaming::binds(trace, replay), + }) + } (None, None) => line.by == Party::Guest, }); let Some((idx, expected)) = found else { panic!("the guest did `{actual}`, which the trace doesn't have"); }; let expected = expected.clone(); - if expected.by != Party::Guest || !self.renaming.matches(&expected, actual) { + if expected.by != Party::Guest || !self.renaming.matches(idx, &expected, actual) { panic!("the trace has `{expected}`, but the guest did `{actual}`"); } - self.renaming.end(&expected); self.done[idx] = true; let Some(task) = task else { return Vec::new(); @@ -282,11 +315,9 @@ impl Replay { if !line.answers_guest() { break; } - let line = line.clone(); - answers.push(self.renaming.to_replay(&line)); - self.renaming.end(&line); - self.done[idx] = true; - if !matches!(line.event, Event::Started(_)) { + let started = matches!(line.event, Event::Started(_)); + answers.push(self.hand(idx)); + if !started { break; } } @@ -302,7 +333,11 @@ mod tests { TaskId { table, id } } fn line(by: Party, event: Event) -> Line { - Line { by, at: None, event } + Line { + by, + at: None, + event, + } } fn call(id: TaskId, func: &str, args: &[&str]) -> Event { Event::Call(Call { @@ -318,6 +353,11 @@ mod tests { value: value.map(str::to_string), }) } + /// The trace a recorder writes of `lines`, with their labels' bindings named apart. + fn recorded(lines: &[Line]) -> Trace { + let mut labels = Labels::default(); + Trace(lines.iter().map(|line| labels.label(line.clone())).collect()) + } /// An export call `t1`, during which the guest calls an import `c1`, then the host drops a /// resource of the guest. @@ -431,14 +471,15 @@ mod tests { name: name.to_string(), }; let (t1, t2) = (task(Table::Exports, 1), task(Table::Exports, 2)); - let trace = Trace(vec![ + let trace = recorded(&[ line(Party::Host, call(t1, "new", &[])), line(Party::Guest, ret(t1, Some("#session-3"))), line(Party::Host, Event::Drop(session("#session-3"))), line(Party::Host, call(t2, "new", &[])), line(Party::Guest, ret(t2, Some("#session-3"))), ]); - let mut replay = Replay::new(trace.clone()); + assert_eq!(trace.0[4].to_string(), "guest: return t2 #session-3-2"); + let mut replay = Replay::new(trace); replay.next_host_line(); // Replay's table numbers the session differently. replay.check(&line(Party::Guest, ret(t1, Some("#session-1")))); @@ -452,8 +493,12 @@ mod tests { #[test] fn replay_ends_labels_handed_on() { - let (t1, t2, t3) = (task(Table::Exports, 1), task(Table::Exports, 2), task(Table::Exports, 3)); - let trace = Trace(vec![ + let (t1, t2, t3) = ( + task(Table::Exports, 1), + task(Table::Exports, 2), + task(Table::Exports, 3), + ); + let trace = recorded(&[ line(Party::Host, call(t1, "new", &[])), line(Party::Guest, ret(t1, Some("#session-3"))), // The host hands the session back, which frees its number. @@ -474,6 +519,32 @@ mod tests { replay.finish(); } + #[test] + fn replay_finds_reused_labels() { + // The guest hands the counter on to the host, which returns another with its number, as + // record mode's trace has it. + let (c1, c2) = (task(Table::Imports, 1), task(Table::Imports, 2)); + let counter = Label { + table: Table::Imports, + name: "#counter-2".to_string(), + }; + let lines = [ + line(Party::Guest, call(c1, "new", &[])), + line(Party::Host, ret(c1, Some("#counter-2"))), + line(Party::Guest, call(c2, "merge", &["#counter-2"])), + line(Party::Host, ret(c2, Some("#counter-2"))), + line(Party::Guest, Event::Drop(counter.clone())), + ]; + let trace = recorded(&lines); + assert_eq!(trace.0[4].to_string(), "guest: drop #counter-2-3"); + // Replay's own labels, which it reuses too. + let mut replay = Replay::new(trace); + assert_eq!(replay.check(&lines[0]), vec![lines[1].clone()]); + assert_eq!(replay.check(&lines[2]), vec![lines[3].clone()]); + replay.check(&lines[4]); + replay.finish(); + } + #[test] #[should_panic(expected = "the trace has `guest: return t2 #session-3`")] fn replay_checks_labels() { diff --git a/crates/trace/src/renaming.rs b/crates/trace/src/renaming.rs index ee5dff2..0c56a42 100644 --- a/crates/trace/src/renaming.rs +++ b/crates/trace/src/renaming.rs @@ -1,19 +1,74 @@ -//! The pairing of a trace's labels with replay's. A label is a handle's number in a generated -//! component's table, and the tables of the recording and of replay differ: besides the values -//! the trace labels, they hold waitable sets and subtasks, which each mode makes its own. So -//! replay compares lines up to renaming: it pairs each label of the trace with the label replay -//! sees for the same value, one to one, per table, from the first line that has both, and requires -//! every later line to agree. A pair ends when its handle leaves the table, which frees its number: -//! at a `Drop`, a future's `Value`, or when the host hands it on, owned, to the guest, in an export -//! call's arguments or an import's result. The rule is the same for every label, whatever it names. +//! The labels of a trace, and their pairing with replay's. A label is a handle's number in a +//! generated component's table, which names the handle until it leaves the table: at a `Drop`, a +//! future's `Value`, or when the host hands it on, owned, to the guest, in an export call's +//! arguments or an import's result. After that, the number may name another handle, as a +//! variable may be bound again. So the recorder names each binding apart, as it records the lines +//! (see `Labels`): the first keeps the number, e.g. `#stream-7`, and the next are `#stream-7-2`, +//! `#stream-7-3`, and so on. A trace's label then names one handle, from its first line to the +//! line that ends it. +//! +//! The tables of the recording and of replay differ: besides the values the trace labels, they +//! hold waitable sets and subtasks, which each mode makes its own. So replay compares lines up to +//! renaming: it pairs each label of the trace with the label replay sees for the same value, one +//! to one, per table, from the first line that has both, and requires every later line to agree. +//! Replay's labels are its own handle numbers, which it reuses too: a replay label can be paired +//! again once the trace's label it was paired with has ended. The rule is the same for every +//! label, whatever it names. use std::collections::HashMap; use crate::{Event, Label, Line, Party, Table}; +/// A trace label's handle in a table, e.g. `(imports, "stream-7")`. +type Handle = (Table, String); + +/// Names the bindings of labels apart, as a recorder records the lines (see the module's doc). #[derive(Default)] +pub struct Labels { + /// The number of bindings of each handle that have ended. + ended: HashMap, +} + +impl Labels { + /// `line`, with each label renamed to its binding's, and the bindings it ends ended. + pub fn label(&mut self, line: Line) -> Line { + let ends = ends_of(&line); + let line = map_labels(&line, |table, handle| { + let ended = *self.ended.get(&(table, handle.to_string()))?; + Some(format!("{handle}-{}", ended + 1)) + }); + for handle in ends { + *self.ended.entry(handle).or_insert(0) += 1; + } + line + } +} + +/// The handle a binding's label names, and which binding of its number it is, as `Labels` names +/// them, e.g. `("stream-7", 2)` for `stream-7-2`, and `("stream-7", 1)` for `stream-7`. A handle +/// ends with its number, and a WIT name's words don't start with a digit. +fn binding_of(label: &str) -> (&str, u32) { + let is_number = |text: &str| !text.is_empty() && text.bytes().all(|b| b.is_ascii_digit()); + match label.rsplit_once('-') { + Some((handle, binding)) + if is_number(binding) && handle.rsplit_once('-').is_some_and(|(_, n)| is_number(n)) => + { + (handle, binding.parse().unwrap()) + } + _ => (label, 1), + } +} + +/// The binding of a handle's number after `label`'s, e.g. `stream-7-2` after `stream-7`. +fn next_binding(label: &str) -> String { + let (handle, binding) = binding_of(label); + format!("{handle}-{}", binding + 1) +} + pub(crate) struct Renaming { - to_replay: HashMap<(Table, String), String>, - to_trace: HashMap<(Table, String), String>, + /// The line each label of the trace ends in. + end_of: HashMap, + to_replay: HashMap, + to_trace: HashMap, } /// A WAVE value, cut into its labels, e.g. `#counter-1` or `#borrow-counter-1`, and the text @@ -78,118 +133,228 @@ fn table_of(event: &Event) -> Table { } } +/// The handles a line's labels name, and the owned ones among those in its values. +fn handles_of(line: &Line) -> (Vec, Vec) { + let table = table_of(&line.event); + let (mut all, mut owned) = (Vec::new(), Vec::new()); + let mut wave = |wave: &String| { + for token in tokens(wave) { + if let Token::Label(label) = token { + let (borrow, handle) = handle_of(label); + all.push((table, handle.to_string())); + if !borrow { + owned.push((table, handle.to_string())); + } + } + } + }; + let mut fields = Vec::new(); + match &line.event { + Event::Call(call) => call.args.iter().for_each(&mut wave), + Event::Return(ret) => ret.value.iter().for_each(&mut wave), + Event::Write(chunk) | Event::Read(chunk) => { + fields.push(&chunk.stream); + wave(&chunk.items); + } + Event::Value(value) => { + fields.push(&value.future); + wave(&value.value); + } + Event::Drop(label) => fields.push(label), + _ => {} + } + all.extend( + fields + .iter() + .map(|label| (label.table, handle_of(&label.name).1.to_string())), + ); + (all, owned) +} + +/// The handles that leave the table with a line: a dropped one, a future whose value was taken, +/// or those the host hands on, owned, to the guest. +/// +/// The party of a call or return says which way its handles move. A host line hands them from +/// a generated component to the guest: an export call's arguments, or an import's result, whose +/// labels end. A guest line hands them to a generated component: an import call's arguments, or +/// an export's result, which come in as new handles. The guest's own handles aren't labels. +fn ends_of(line: &Line) -> Vec { + let handle = |label: &Label| (label.table, handle_of(&label.name).1.to_string()); + match &line.event { + Event::Drop(label) => vec![handle(label)], + Event::Value(value) => vec![handle(&value.future)], + Event::Call(_) | Event::Return(_) if line.by == Party::Host => handles_of(line).1, + _ => Vec::new(), + } +} + +/// `wave` with its labels renamed by `rename`, of the handle a label names, if it has one. +fn rename(wave: &str, rename: impl Fn(&str) -> Option) -> String { + tokens(wave) + .into_iter() + .map(|token| match token { + Token::Text(text) => text.to_string(), + Token::Label(label) => match (handle_of(label), rename(handle_of(label).1)) { + ((true, _), Some(handle)) => format!("#borrow-{handle}"), + ((false, _), Some(handle)) => format!("#{handle}"), + (_, None) => label.to_string(), + }, + }) + .collect() +} + +/// `line` with its labels renamed by `rename`, of a handle in a table, if it has a new name. +fn map_labels(line: &Line, rename: impl Fn(Table, &str) -> Option) -> Line { + let table = table_of(&line.event); + let wave = |wave: &String| self::rename(wave, |handle| rename(table, handle)); + let label = |label: &Label| Label { + table: label.table, + name: self::rename(&label.name, |handle| rename(label.table, handle)), + }; + let event = match &line.event { + Event::Call(call) => Event::Call(crate::Call { + args: call.args.iter().map(wave).collect(), + ..call.clone() + }), + Event::Return(ret) => Event::Return(crate::ReturnValue { + value: ret.value.as_ref().map(wave), + ..ret.clone() + }), + Event::Write(chunk) => Event::Write(crate::Chunk { + stream: label(&chunk.stream), + items: wave(&chunk.items), + }), + Event::Read(chunk) => Event::Read(crate::Chunk { + stream: label(&chunk.stream), + items: wave(&chunk.items), + }), + Event::Value(value) => Event::Value(crate::FutureValue { + future: label(&value.future), + value: wave(&value.value), + }), + Event::Drop(handle) => Event::Drop(label(handle)), + event => event.clone(), + }; + Line { + event, + ..line.clone() + } +} + impl Renaming { - /// Pairs the trace's label `trace` with replay's `replay`, or returns false if either is - /// paired with another. - fn pair(&mut self, table: Table, trace: &str, replay: &str) -> bool { - let ((trace_borrow, trace), (replay_borrow, replay)) = (handle_of(trace), handle_of(replay)); + /// Panics if a label of the trace names a handle after its end: a trace whose bindings + /// `Labels` didn't name apart. + pub(crate) fn new(lines: &[Line]) -> Self { + let mut labels = Labels::default(); + let mut end_of = HashMap::new(); + for (idx, line) in lines.iter().enumerate() { + assert!( + labels.label(line.clone()) == *line, + "the trace has `{line}`, with a label after the line that ended it" + ); + for handle in ends_of(line) { + end_of.insert(handle, idx); + } + } + Renaming { + end_of, + to_replay: HashMap::new(), + to_trace: HashMap::new(), + } + } + /// Whether the trace's label of `handle` has ended before line `idx`. + fn has_ended(&self, handle: &Handle, idx: usize) -> bool { + self.end_of.get(handle).is_some_and(|end| *end < idx) + } + /// Pairs the trace's label `trace` with replay's `replay`, at line `idx`, or returns false if + /// either is paired with another, which is still live. + fn pair(&mut self, idx: usize, table: Table, trace: &str, replay: &str) -> bool { + let ((trace_borrow, trace), (replay_borrow, replay)) = + (handle_of(trace), handle_of(replay)); if trace_borrow != replay_borrow { return false; } - let key = |label: &str| (table, label.to_string()); - match (self.to_replay.get(&key(trace)), self.to_trace.get(&key(replay))) { - (Some(paired), _) => paired == replay, - (None, Some(_)) => false, - (None, None) => { - self.to_replay.insert(key(trace), replay.to_string()); - self.to_trace.insert(key(replay), trace.to_string()); - true + let trace = (table, trace.to_string()); + if let Some(paired) = self.to_replay.get(&trace) { + return paired == replay; + } + let replay = (table, replay.to_string()); + if let Some(other) = self.to_trace.get(&replay) { + let other = (table, other.clone()); + if !self.has_ended(&other, idx) { + return false; } + // Replay reused the number of a handle that ended, as the trace says. + self.to_replay.remove(&other); } + self.to_replay.insert(trace.clone(), replay.1.clone()); + self.to_trace.insert(replay, trace.1); + true } - /// Ends the pair of the trace's label `trace`, whose handle has ended. - fn unpair(&mut self, table: Table, trace: &str) { - let (_, trace) = handle_of(trace); - if let Some(replay) = self.to_replay.remove(&(table, trace.to_string())) { - self.to_trace.remove(&(table, replay)); - } + /// Pairs the trace's label of a stream or future replay makes, from the value of the line that + /// hands it on, with replay's, of the end it keeps: the label's next binding, e.g. the writer + /// of the stream a mock returns, whose number labeled the reader in that line. Returns false + /// if no line hands it on, or either is paired with another. + pub(crate) fn pair_next(&mut self, trace: &Label, replay: &Label) -> bool { + let handle = (trace.table, handle_of(&trace.name).1.to_string()); + let Some(end) = self.end_of.get(&handle).copied() else { + return false; + }; + trace.table == replay.table + && self.pair(end + 1, trace.table, &next_binding(&handle.1), &replay.name) } - /// Whether the trace's WAVE value `trace` is replay's `replay`, up to the pairing, which it - /// extends. - fn unify(&mut self, table: Table, trace: &str, replay: &str) -> bool { + /// Whether the trace's WAVE value `trace`, in line `idx`, is replay's `replay`, up to the + /// pairing, which it extends. + fn unify(&mut self, idx: usize, table: Table, trace: &str, replay: &str) -> bool { let (trace, replay) = (tokens(trace), tokens(replay)); trace.len() == replay.len() && trace.iter().zip(&replay).all(|tokens| match tokens { (Token::Text(trace), Token::Text(replay)) => trace == replay, - (Token::Label(trace), Token::Label(replay)) => self.pair(table, trace, replay), + (Token::Label(trace), Token::Label(replay)) => self.pair(idx, table, trace, replay), _ => false, }) } - /// `wave` with its labels renamed by `rename`, of the handle a label names, if it has one. - fn rename(wave: &str, rename: impl Fn(&str) -> Option) -> String { - tokens(wave) - .into_iter() - .map(|token| match token { - Token::Text(text) => text.to_string(), - Token::Label(label) => match (handle_of(label), rename(handle_of(label).1)) { - ((true, _), Some(handle)) => format!("#borrow-{handle}"), - ((false, _), Some(handle)) => format!("#{handle}"), - (_, None) => label.to_string(), - }, - }) - .collect() - } - fn to_replay_wave(&self, table: Table, wave: &str) -> String { - Self::rename(wave, |handle| self.to_replay.get(&(table, handle.to_string())).cloned()) - } - fn to_replay_label(&self, label: &Label) -> Label { - Label { - table: label.table, - name: self.to_replay_wave(label.table, &label.name), - } - } - /// Replay's label, e.g. a job's for the stream it keeps, as the trace's. - pub(crate) fn to_trace_label(&self, label: &Label) -> Label { - let name = Self::rename(&label.name, |handle| { - self.to_trace.get(&(label.table, handle.to_string())).cloned() - }); - Label { + /// The trace's label that replay's label, e.g. a job's for the stream it keeps, is paired + /// with, if any. + pub(crate) fn to_trace(&self, label: &Label) -> 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 ZR93I8PH3=FHemG!ngM6-N_n^8{{sHcVrKvV diff --git a/assets/recorder.wasm b/assets/recorder.wasm index d781b0aa64cd63ea052d6182e9dce9cf2ad932a8..851032e0b64c0a58a92a8c3ef856aee4ddc7056b 100644 GIT binary patch delta 84370 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 delta 75139 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 PR->O8u;;Fj_iFtgv4BFF diff --git a/components/recorder/src/lib.rs b/components/recorder/src/lib.rs index 6336ae7..6fd11cb 100644 --- a/components/recorder/src/lib.rs +++ b/components/recorder/src/lib.rs @@ -18,14 +18,21 @@ use trace::{Label, Labels, Line, LineKey, Replay, Trace}; thread_local! { // The bindings of the labels recorded so far, which each line's are named apart by. static LABELS: RefCell = RefCell::new(Labels::default()); + // Whether to print the lines as text, with `PROXY_TRACE=text`, e.g. to read a fuzz run, + // rather than in their stored form, which replay reads. + static AS_TEXT: bool = std::env::var("PROXY_TRACE").is_ok_and(|format| format == "text"); } struct Component; impl bindings::exports::proxy::recorder::record::Guest for Component { - // Prints the stored form of the line, so that stdout is the trace, also of a run that traps. + // Prints the line, so that stdout is the trace, also of a run that traps. fn record(line: Line) { let line = LABELS.with_borrow_mut(|labels| labels.label(line)); - println!("{}", line.to_json()); + if AS_TEXT.with(|as_text| *as_text) { + println!("{line}"); + } else { + println!("{}", line.to_json()); + } } fn fail(message: String) { panic!("{message}"); diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 56fca3a..49c57b0 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -118,7 +118,8 @@ impl Trace { /// Reads a trace from its stored form, ignoring the lines that aren't trace lines, e.g. the /// guest's output: those that aren't JSON objects with a `by` and an `event`. /// - /// Panics on a trace line that doesn't parse, e.g. of another version of the format. + /// Panics on a trace line that doesn't parse, e.g. of another version of the format, and on + /// text without trace lines, e.g. a trace printed as text. pub fn parse(text: &str) -> Self { let lines = text.lines().filter_map(|text| { let value: serde_json::Value = serde_json::from_str(text).ok()?; @@ -131,7 +132,12 @@ impl Trace { Err(err) => panic!("invalid trace line `{text}`: {err}"), } }); - Trace(lines.collect()) + let trace = Trace(lines.collect()); + assert!( + !trace.0.is_empty() || text.trim().is_empty(), + "no trace lines: was it printed with `PROXY_TRACE=text`?" + ); + trace } } @@ -401,6 +407,13 @@ mod tests { Trace::parse(r##"{"by":"Guest","at":null,"event":{"Drop":{"handle":"#x-1"}}}"##); } + #[test] + #[should_panic(expected = "no trace lines")] + fn stored_form_as_text() { + let text: Vec<_> = p2_trace().0.iter().map(Line::to_string).collect(); + Trace::parse(&text.join("\n")); + } + #[test] fn display() { let lines: Vec<_> = p2_trace().0.iter().map(Line::to_string).collect(); diff --git a/crates/trace/src/renaming.rs b/crates/trace/src/renaming.rs index 0c56a42..0a7a026 100644 --- a/crates/trace/src/renaming.rs +++ b/crates/trace/src/renaming.rs @@ -14,7 +14,7 @@ //! Replay's labels are its own handle numbers, which it reuses too: a replay label can be paired //! again once the trace's label it was paired with has ended. The rule is the same for every //! label, whatever it names. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::{Event, Label, Line, Party, Table}; @@ -22,24 +22,53 @@ use crate::{Event, Label, Line, Party, Table}; type Handle = (Table, String); /// Names the bindings of labels apart, as a recorder records the lines (see the module's doc). +/// +/// A line can come after the guest acted on a handle it brings in: the host records a copy once +/// its job sees it complete, e.g. a future's value with a mock in it, which the guest may have +/// taken and dropped by then. Lines about different handles may come in any order. So a `Drop` +/// or `Value` of a label that no line has mentioned yet ends the binding of the line that brings +/// it in, which comes later. A line that brings a handle in and hands it on, e.g. a mock an +/// import returns, ends it at its first mention. #[derive(Default)] pub struct Labels { /// The number of bindings of each handle that have ended. ended: HashMap, + /// The handles the lines so far mention. + seen: HashSet, + /// The handles a line ended before any line brought them in. + early: HashSet, } impl Labels { /// `line`, with each label renamed to its binding's, and the bindings it ends ended. pub fn label(&mut self, line: Line) -> Line { - let ends = ends_of(&line); - let line = map_labels(&line, |table, handle| { + let renamed = map_labels(&line, |table, handle| { let ended = *self.ended.get(&(table, handle.to_string()))?; Some(format!("{handle}-{}", ended + 1)) }); - for handle in ends { - *self.ended.entry(handle).or_insert(0) += 1; + let mentioned = handles_of(&line).0; + // The line that brings in a handle an earlier line ended, whose binding then ends. + let late: Vec<_> = mentioned + .iter() + .filter(|handle| self.early.remove(*handle)) + .cloned() + .collect(); + for handle in &late { + *self.ended.entry(handle.clone()).or_insert(0) += 1; } - line + for handle in ends_of(&line) { + if late.contains(&handle) { + continue; + } + let brings_in = matches!(line.event, Event::Call(_) | Event::Return(_)); + if brings_in || self.seen.contains(&handle) { + *self.ended.entry(handle).or_insert(0) += 1; + } else { + self.early.insert(handle); + } + } + self.seen.extend(mentioned); + renamed } } @@ -482,6 +511,25 @@ mod tests { assert_eq!(binding_of("error-context-1"), ("error-context-1", 1)); } + #[test] + fn labels_end_before_the_line_that_brings_them_in() { + // The guest dropped a mock in a value the host's line about it comes after. + let mut labels = Labels::default(); + let drop = Line { + by: Party::Guest, + at: None, + event: Event::Drop(Label { + table: Table::Exports, + name: "#body-2".to_string(), + }), + }; + let value = guest_return(1, "ok(#body-2)"); + assert_eq!(labels.label(drop.clone()), drop); + assert_eq!(labels.label(value.clone()), value); + // Its binding has ended, then. + assert_eq!(labels.label(value), guest_return(1, "ok(#body-2-2)")); + } + #[test] fn replay_labels_pair_again_once_the_trace_ends_theirs() { let lines = [ diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index e3b06cf..6bc7e17 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -51,7 +51,9 @@ in component-ui's `docs/p3.md`. Backward compatibility with p2 traces is not a goal; the format is redesigned. A trace is the list of lines the proxy observed, in the order it observed them. Replay relies on only part of that -order; see Order, below. How a trace is stored is up to the implementation. +order; see Order, below. How a trace is stored is up to the implementation: the recorders write +one JSON line per line, and the bundled recorder prints text instead, e.g. `host @2: call t1 …`, +with `PROXY_TRACE=text`, for reading a fuzz run. The format is defined once, as the WIT interface `proxy:recorder/types` ([`assets/recorder.wit`](../assets/recorder.wit)), which the generated components use to record @@ -262,7 +264,10 @@ Limits: names each binding apart, as it records each line, from the lines before it alone, which a run that traps leaves too: the first keeps the number, e.g. `#stream-7`, and the next are `#stream-7-2`, `#stream-7-3`, and so on. A trace's label then names one handle, whatever order - replay's jobs process the lines in. Replay's labels are its own numbers, which it reuses too: a + replay's jobs process the lines in. A host line can come after the guest acted on a handle the + line brings in, as the host records a copy once its job sees it complete: e.g. a future's value + with a mock in it, which the guest took and dropped first. So a `Drop` or a `Value` of a label + no line has mentioned yet ends the binding of the line that brings it in. Replay's labels are its own numbers, which it reuses too: a replay label is paired again once the trace's label it was paired with has ended. A stream or future replay makes, from a value the trace hands out, first appears in a host line, which replay doesn't check, so replay pairs its label explicitly, with the binding after that line: @@ -358,20 +363,22 @@ Replay has to know when to perform each host line. There are three kinds of line performs them when the host gets to them, so `at: n` says they happened in step `n`. For a copy, it is the step in which the host made its side, e.g. issued the write the guest then read, rather than the one in which the host saw it complete, which may come after the guest reacted - to it. In replay, - the executor counts its steps the same way, and a job holding such a line waits until the count - reaches `n`. There is no host to wake the executor, so a job waiting for a later step makes the - executor wake itself, by yielding, and its next run is the next step. + to it. In replay, the executor counts its steps the same way, and a job holding such a line + waits until the count reaches `n`. There is no host to wake the executor, so a job waiting for + a later step makes the executor wake itself, by yielding, and its next run is the next step. + A task that waits for a step from outside the executor, e.g. the mock of a STARTED call, holds + the executor from the run it waited for until the task resumes: otherwise a run, e.g. for a + guest's drop, could come in between, which replay, knowing only the step, would place + differently, as it waits by waking the executor itself, which changes the order in which + wasmtime delivers the guest's events. **Export calls** arrive at the exports component, from the host, so their arrival is a step of its own. On each arrival, the exports component calls `step` on `proxy:conversion/executor`, a sync function that increments the counter and returns it, as the export `Call`'s `at`; the executor's later runs are numbered after it. In replay, `start` makes an export call recorded at step `n` once -the count reaches `n - 1`, by calling `step` and then the export. The wait and the step are one -call, `arrive-after`, which holds the executor from the run it waited for until it counts the -step: otherwise a run, e.g. for a guest's drop, could come in between, and replay, which knows -only the step, would wait for that run instead, by waking the executor itself, which changes the -order in which wasmtime delivers the guest's events. An arrival can't fall within a run of the +the count reaches `n - 1`, by calling `step` and then the export. The wait and the step are one call, `step-after`, which takes the step in the imports +component's task as soon as its wait resumes, while the executor is still held, as above. As two +calls, the executor could run between the wait's return and `start`'s call of `step`. An arrival can't fall within a run of the executor: the host only calls an export while no wasm is running, unless something suspends where the proxy can't see it, which the requirements below rule out. The host's drops of the guest's resources, which `start` makes too, take a step the same way. @@ -433,7 +440,7 @@ trace, the two results could reach the guest before or after a different one of - **The same executor in record and replay**, with only the leaves different: host calls in record, futures that the trace makes ready in replay. Its work in a step depends only on its state and the trace's lines for that step. The same goes for `start`: fuzz mode's waits for - the steps it chooses before an export call, through `arrive-after`, as replay's waits for the + the steps it chooses before an export call, through `step-after`, as replay's waits for the trace's, so that the two make the same tasks. - **Steps in replay where the host woke the executor in the recording.** Replay has no host, so a job that waits for a later step makes the executor yield, and come back as the next step. @@ -441,7 +448,7 @@ trace, the two results could reach the guest before or after a different one of - **No suspension the proxy can't see**: no calls to other components where the recording calls the host, no blocking WASI 0.2 calls, and no epoch deadlines that yield, as above. - **One clock for both components.** The exports component takes its steps from the same - counter, through `step` and `arrive-after`, as [Steps](#steps) describes. + counter, through `step` and `step-after`, as [Steps](#steps) describes. ### What recording can't reach @@ -617,7 +624,9 @@ shim](#portability) could provide; see No guest task on import calls, in - **A fixpoint test**: record a run, replay it while recording, and require the two traces to be equal. The spike does this, for its scenarios. - **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2 and p3 fixtures, and - replays each trace. + replays each trace, which has to end as the fuzzing run did: to the end, or in the guest's trap, + e.g. a panic on an error the fuzzed host returned, which is what fuzzing finds. A trap in a + generated component or the recorder, e.g. a divergence, fails it (`tests/run-wasm.sh`). - **Enumerated scenarios**: for small guests, every placement of host completions across steps, rather than random delays, and guests that merge nondeterministically, which are the ones steps exist for. diff --git a/src/codegen/fuzz.rs b/src/codegen/fuzz.rs index 7fadfd7..01d89df 100644 --- a/src/codegen/fuzz.rs +++ b/src/codegen/fuzz.rs @@ -26,8 +26,8 @@ impl State { let params = params_to_wave(kind.as_ref(), &args); // What the host does, rather than returns: how it reads the streams and futures, and when // an async import returns, at once (RETURNED), or after the executor ran a few times - // (STARTED), in whose last run the `return` happens. Random, rather than drawn from the - // text of the arguments, whose first byte is mostly the `#` of a label. + // (STARTED), in whose last run the `return` happens. Random for each call, rather than + // drawn from its arguments, as the same call may take its time differently. let delay = if is_async { quote! { let __runs = __host.int_in_range(0..=3u8).unwrap_or(0); @@ -71,8 +71,9 @@ impl State { #[allow(unused_variables)] #sig { #preamble - let __buf = __params.join(",") + #display_name; - let mut u = Unstructured::new(&__buf.as_bytes()); + // The result, drawn from the arguments (see `fuzz_data`). + let __data = fuzz_data(&(__params.join(",") + #display_name)); + let mut u = Unstructured::new(&__data); let res = #gen_ret; #ret res @@ -107,7 +108,7 @@ impl State { // the jobs of their streams and futures come after the call that hands them out. let args = quote! { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after(proxy::conversion::executor::current_step() + __runs).await; + let __at = proxy::conversion::executor::step_after(proxy::conversion::executor::current_step() + __runs).await; let mut __params: Vec = Vec::new(); #( let #arg_names: #arg_tys = Fuzz::fuzz(&mut u).unwrap(); diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 175cf15..bd12c47 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -530,7 +530,7 @@ impl State { ("step", _) => quote! { next_step() }, ("current_step", _) => quote! { current_step() }, ("wait_until_step", _) => quote! { wait_until_step(step).await }, - ("arrive_after", _) => quote! { arrive_after(step).await }, + ("step_after", _) => quote! { step_after(step).await }, ("host_drops", Mode::Replay | Mode::Fuzz) => { quote! { MOVING.with(|moving| moving.set(dropping)) } } diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 6a8f933..77974ab 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -145,7 +145,7 @@ impl State { // A line in step `at` follows the step before it, and takes its own step, as // an export call's arrival does. if let Some(at) = line.at { - let step = proxy::conversion::executor::arrive_after(at - 1).await; + let step = proxy::conversion::executor::step_after(at - 1).await; assert_eq!(step, at, "replay came to {:?} in step {step}", line.event); } match line.event { diff --git a/src/traits/fuzz.rs b/src/traits/fuzz.rs index dcb59a0..477eb1c 100644 --- a/src/traits/fuzz.rs +++ b/src/traits/fuzz.rs @@ -199,6 +199,33 @@ impl Trait for FuzzTrait { #[allow(dead_code)] fn consume(self, _u: &mut Unstructured<'_>) {} } + thread_local! { + // The run's seed, which `fuzz_data` hashes with its text, so that runs differ. + static FUZZ_SEED: u64 = { + let random = proxy::util::debug::get_random(); + u64::from_le_bytes(random[..8].try_into().unwrap()) + }; + } + /// Fuzz data drawn from `text`, e.g. a call's arguments: the same for the same text in + /// a run, so that the host answers alike, and different in another run. The text's + /// hash seeds splitmix64, which makes as much data as `get-random` gives. + #[allow(dead_code)] + fn fuzz_data(text: &str) -> Vec { + use std::hash::{Hash, Hasher}; + let mut hasher = std::hash::DefaultHasher::new(); + FUZZ_SEED.with(|seed| seed.hash(&mut hasher)); + text.hash(&mut hasher); + let mut state = hasher.finish(); + (0..128) + .flat_map(|_| { + state = state.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + (z ^ (z >> 31)).to_le_bytes() + }) + .collect() + } impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { // A constructor's. diff --git a/tests/run-wasm.sh b/tests/run-wasm.sh new file mode 100755 index 0000000..84b98a2 --- /dev/null +++ b/tests/run-wasm.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Runs `wasmtime "$@"`, printing its output and saving it to the file `$1`, and exits with how +# the run ended: +# +# 0 it ran to the end; +# 2 the main component trapped, e.g. a guest's panic on an error the fuzzed host returned, +# which is what fuzzing finds, rather than a failure of the proxy; +# 1 anything else, a failure of the proxy: a trap in a generated component, which has no +# name, or in the recorder, which reports the generated components' panics and replay's +# divergences; or an error without a backtrace, e.g. "deadlock detected". +out=$1 +shift +wasmtime "$@" 2>&1 | tee "$out" +[ "${PIPESTATUS[0]}" -eq 0 ] && exit 0 +# The frame that trapped, the first of the backtrace. +frame=$(grep -m1 -A1 "wasm backtrace:" "$out" | tail -n +2) +case "$frame" in + "" | *"!"* | *"recorder.wasm!"* | *"recorder.one_byte_page.wasm!"*) exit 1 ;; + *) exit 2 ;; +esac diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index d687b6d..ad93e19 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -35,8 +35,8 @@ impl exports::proxy::conversion::executor::Guest for Stub { async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } - async fn arrive_after(step: u32) -> u32 { - arrive_after(step).await + async fn step_after(step: u32) -> u32 { + step_after(step).await } fn host_drops(dropping: bool) -> () { let _ = dropping; @@ -611,8 +611,8 @@ thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = - const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < bool > = const - { std::cell::Cell::new(false) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const + { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -631,16 +631,13 @@ fn current_step() -> u32 { #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { let mut left = runs; - wait_for_run( - move || { - if left == 0 { - return true; - } - left -= 1; - false - }, - false, - ) + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) .await } /// Waits, from any task, until a run of the executor in `step` or later, or returns at once if @@ -651,23 +648,15 @@ async fn wait_until_step(step: u32) { if current_step() >= step { return; } - wait_for_run(move || current_step() >= step, false).await; + wait_for_run(move || current_step() >= step).await; } /// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line -/// `start` makes, e.g. an export call's arrival, which it returns. The executor doesn't run in -/// between, e.g. for a guest's event, so that the step follows the run it waited for, which -/// replay waits for from the step alone. +/// `start` makes, e.g. an export call's arrival, which it returns: the one after the run it +/// waited for, as the executor doesn't run in between (see `wait_for_run`). #[allow(dead_code)] -async fn arrive_after(step: u32) -> u32 { - if current_step() < step { - wait_for_run(move || current_step() >= step, true).await; - } - HELD.set(false); - let step = next_step(); - if WOKEN_WHILE_HELD.replace(false) { - wake_executor(); - } - step +async fn step_after(step: u32) -> u32 { + wait_until_step(step).await; + next_step() } /// Waits, in a job of the executor, until its run in `step`, waking the executor itself until /// then, e.g. replay's job for a copy recorded in that step. @@ -690,24 +679,40 @@ async fn reach_step(step: u32) { .await } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job -/// asks in each run, waking the executor itself in between, as no host event would. With `hold`, -/// the executor doesn't run again until `arrive_after` releases it. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u32 { +/// asks in each run, waking the executor itself in between, as no host event would. +/// +/// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so +/// that what the task does next, e.g. return a STARTED call, or take an export call's step, +/// follows the run it waited for, in record and replay alike: replay knows only the step. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, + held: bool, + } + /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. + /// the mock of an import the guest cancelled. + struct Waiter(std::rc::Rc>); + impl Drop for Waiter { + fn drop(&mut self) { + if std::mem::take(&mut self.0.borrow_mut().held) { + HELD.set(HELD.get() - 1); + if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + } + } } - let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_shared = shared.clone(); + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job_shared = waiter.0.clone(); spawn( std::future::poll_fn(move |_| { if done() { - if hold { - HELD.set(true); - } + HELD.set(HELD.get() + 1); let mut shared = job_shared.borrow_mut(); shared.step = Some(current_step()); + shared.held = true; if let Some(waker) = shared.waker.take() { waker.wake(); } @@ -717,8 +722,8 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u std::task::Poll::Pending }), ); - std::future::poll_fn(move |cx| { - let mut shared = shared.borrow_mut(); + let step = std::future::poll_fn(|cx| { + let mut shared = waiter.0.borrow_mut(); match shared.step { Some(step) => std::task::Poll::Ready(step), None => { @@ -727,7 +732,9 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u } } }) - .await + .await; + drop(waiter); + step } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -750,7 +757,7 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { - if HELD.get() { + if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); return std::task::Poll::Pending; diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index 69a0e4a..42041ad 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -47,7 +47,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 1usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -89,7 +89,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 2usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -128,7 +128,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 3usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -167,7 +167,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 4usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -548,6 +548,30 @@ trait Fuzz: Sized { #[allow(dead_code)] fn consume(self, _u: &mut Unstructured<'_>) {} } +thread_local! { + static FUZZ_SEED : u64 = { let random = proxy::util::debug::get_random(); + u64::from_le_bytes(random[..8].try_into().unwrap()) }; +} +/// Fuzz data drawn from `text`, e.g. a call's arguments: the same for the same text in +/// a run, so that the host answers alike, and different in another run. The text's +/// hash seeds splitmix64, which makes as much data as `get-random` gives. +#[allow(dead_code)] +fn fuzz_data(text: &str) -> Vec { + use std::hash::{Hash, Hasher}; + let mut hasher = std::hash::DefaultHasher::new(); + FUZZ_SEED.with(|seed| seed.hash(&mut hasher)); + text.hash(&mut hasher); + let mut state = hasher.finish(); + (0..128) + .flat_map(|_| { + state = state.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + (z ^ (z >> 31)).to_le_bytes() + }) + .collect() +} impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 6693c2c..469d7e8 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -39,8 +39,8 @@ impl exports::proxy::conversion::executor::Guest for Stub { async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } - async fn arrive_after(step: u32) -> u32 { - arrive_after(step).await + async fn step_after(step: u32) -> u32 { + step_after(step).await } fn host_drops(dropping: bool) -> () { MOVING.with(|moving| moving.set(dropping)) @@ -68,8 +68,10 @@ impl exports::test::host::types::Guest for Stub { MOVING.with(|moving| moving.set(true)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:host/types@0.1.0.make-point"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data( + &(__params.join(",") + "test:host/types@0.1.0.make-point"), + ); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -103,8 +105,8 @@ impl exports::test::host::types::Guest for Stub { MOVING.with(|moving| moving.set(true)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:host/types@0.1.0.check"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data(&(__params.join(",") + "test:host/types@0.1.0.check")); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -136,8 +138,8 @@ impl exports::test::host::types::Guest for Stub { MOVING.with(|moving| moving.set(true)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:host/types@0.1.0.open"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data(&(__params.join(",") + "test:host/types@0.1.0.open")); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -169,8 +171,10 @@ impl exports::test::host::types::GuestCounter for MockedResource { MOVING.with(|moving| moving.set(true)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "new test:host/types/counter@0.1.0"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data( + &(__params.join(",") + "new test:host/types/counter@0.1.0"), + ); + let mut u = Unstructured::new(&__data); let res = MockedResource { name: "counter".to_string(), ..Fuzz::fuzz(&mut u).unwrap() @@ -204,8 +208,10 @@ impl exports::test::host::types::GuestCounter for MockedResource { MOVING.with(|moving| moving.set(true)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:host/types/counter@0.1.0.get"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data( + &(__params.join(",") + "test:host/types/counter@0.1.0.get"), + ); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -268,8 +274,10 @@ impl exports::test::host::types::GuestCounter for MockedResource { drop((b,)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:host/types/counter@0.1.0.merge"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data( + &(__params.join(",") + "test:host/types/counter@0.1.0.merge"), + ); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -342,8 +350,8 @@ impl exports::test::log0_2_0::sink::Guest for Stub { MOVING.with(|moving| moving.set(true)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:log/sink@0.2.0.write"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data(&(__params.join(",") + "test:log/sink@0.2.0.write")); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -622,6 +630,30 @@ trait Fuzz: Sized { #[allow(dead_code)] fn consume(self, _u: &mut Unstructured<'_>) {} } +thread_local! { + static FUZZ_SEED : u64 = { let random = proxy::util::debug::get_random(); + u64::from_le_bytes(random[..8].try_into().unwrap()) }; +} +/// Fuzz data drawn from `text`, e.g. a call's arguments: the same for the same text in +/// a run, so that the host answers alike, and different in another run. The text's +/// hash seeds splitmix64, which makes as much data as `get-random` gives. +#[allow(dead_code)] +fn fuzz_data(text: &str) -> Vec { + use std::hash::{Hash, Hasher}; + let mut hasher = std::hash::DefaultHasher::new(); + FUZZ_SEED.with(|seed| seed.hash(&mut hasher)); + text.hash(&mut hasher); + let mut state = hasher.finish(); + (0..128) + .flat_map(|_| { + state = state.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + (z ^ (z >> 31)).to_le_bytes() + }) + .collect() +} impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { @@ -805,8 +837,8 @@ thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = - const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < bool > = const - { std::cell::Cell::new(false) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const + { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -825,16 +857,13 @@ fn current_step() -> u32 { #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { let mut left = runs; - wait_for_run( - move || { - if left == 0 { - return true; - } - left -= 1; - false - }, - false, - ) + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) .await } /// Waits, from any task, until a run of the executor in `step` or later, or returns at once if @@ -845,23 +874,15 @@ async fn wait_until_step(step: u32) { if current_step() >= step { return; } - wait_for_run(move || current_step() >= step, false).await; + wait_for_run(move || current_step() >= step).await; } /// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line -/// `start` makes, e.g. an export call's arrival, which it returns. The executor doesn't run in -/// between, e.g. for a guest's event, so that the step follows the run it waited for, which -/// replay waits for from the step alone. +/// `start` makes, e.g. an export call's arrival, which it returns: the one after the run it +/// waited for, as the executor doesn't run in between (see `wait_for_run`). #[allow(dead_code)] -async fn arrive_after(step: u32) -> u32 { - if current_step() < step { - wait_for_run(move || current_step() >= step, true).await; - } - HELD.set(false); - let step = next_step(); - if WOKEN_WHILE_HELD.replace(false) { - wake_executor(); - } - step +async fn step_after(step: u32) -> u32 { + wait_until_step(step).await; + next_step() } /// Waits, in a job of the executor, until its run in `step`, waking the executor itself until /// then, e.g. replay's job for a copy recorded in that step. @@ -884,24 +905,40 @@ async fn reach_step(step: u32) { .await } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job -/// asks in each run, waking the executor itself in between, as no host event would. With `hold`, -/// the executor doesn't run again until `arrive_after` releases it. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u32 { +/// asks in each run, waking the executor itself in between, as no host event would. +/// +/// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so +/// that what the task does next, e.g. return a STARTED call, or take an export call's step, +/// follows the run it waited for, in record and replay alike: replay knows only the step. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, + held: bool, + } + /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. + /// the mock of an import the guest cancelled. + struct Waiter(std::rc::Rc>); + impl Drop for Waiter { + fn drop(&mut self) { + if std::mem::take(&mut self.0.borrow_mut().held) { + HELD.set(HELD.get() - 1); + if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + } + } } - let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_shared = shared.clone(); + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job_shared = waiter.0.clone(); spawn( std::future::poll_fn(move |_| { if done() { - if hold { - HELD.set(true); - } + HELD.set(HELD.get() + 1); let mut shared = job_shared.borrow_mut(); shared.step = Some(current_step()); + shared.held = true; if let Some(waker) = shared.waker.take() { waker.wake(); } @@ -911,8 +948,8 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u std::task::Poll::Pending }), ); - std::future::poll_fn(move |cx| { - let mut shared = shared.borrow_mut(); + let step = std::future::poll_fn(|cx| { + let mut shared = waiter.0.borrow_mut(); match shared.step { Some(step) => std::task::Poll::Ready(step), None => { @@ -921,7 +958,9 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u } } }) - .await + .await; + drop(waiter); + step } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -944,7 +983,7 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { - if HELD.get() { + if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); return std::task::Poll::Pending; diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index 0a08627..e75aacd 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -35,8 +35,8 @@ impl exports::proxy::conversion::executor::Guest for Stub { async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } - async fn arrive_after(step: u32) -> u32 { - arrive_after(step).await + async fn step_after(step: u32) -> u32 { + step_after(step).await } fn host_drops(dropping: bool) -> () { let _ = dropping; @@ -581,8 +581,8 @@ thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = - const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < bool > = const - { std::cell::Cell::new(false) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const + { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -601,16 +601,13 @@ fn current_step() -> u32 { #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { let mut left = runs; - wait_for_run( - move || { - if left == 0 { - return true; - } - left -= 1; - false - }, - false, - ) + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) .await } /// Waits, from any task, until a run of the executor in `step` or later, or returns at once if @@ -621,23 +618,15 @@ async fn wait_until_step(step: u32) { if current_step() >= step { return; } - wait_for_run(move || current_step() >= step, false).await; + wait_for_run(move || current_step() >= step).await; } /// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line -/// `start` makes, e.g. an export call's arrival, which it returns. The executor doesn't run in -/// between, e.g. for a guest's event, so that the step follows the run it waited for, which -/// replay waits for from the step alone. +/// `start` makes, e.g. an export call's arrival, which it returns: the one after the run it +/// waited for, as the executor doesn't run in between (see `wait_for_run`). #[allow(dead_code)] -async fn arrive_after(step: u32) -> u32 { - if current_step() < step { - wait_for_run(move || current_step() >= step, true).await; - } - HELD.set(false); - let step = next_step(); - if WOKEN_WHILE_HELD.replace(false) { - wake_executor(); - } - step +async fn step_after(step: u32) -> u32 { + wait_until_step(step).await; + next_step() } /// Waits, in a job of the executor, until its run in `step`, waking the executor itself until /// then, e.g. replay's job for a copy recorded in that step. @@ -660,24 +649,40 @@ async fn reach_step(step: u32) { .await } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job -/// asks in each run, waking the executor itself in between, as no host event would. With `hold`, -/// the executor doesn't run again until `arrive_after` releases it. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u32 { +/// asks in each run, waking the executor itself in between, as no host event would. +/// +/// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so +/// that what the task does next, e.g. return a STARTED call, or take an export call's step, +/// follows the run it waited for, in record and replay alike: replay knows only the step. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, + held: bool, + } + /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. + /// the mock of an import the guest cancelled. + struct Waiter(std::rc::Rc>); + impl Drop for Waiter { + fn drop(&mut self) { + if std::mem::take(&mut self.0.borrow_mut().held) { + HELD.set(HELD.get() - 1); + if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + } + } } - let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_shared = shared.clone(); + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job_shared = waiter.0.clone(); spawn( std::future::poll_fn(move |_| { if done() { - if hold { - HELD.set(true); - } + HELD.set(HELD.get() + 1); let mut shared = job_shared.borrow_mut(); shared.step = Some(current_step()); + shared.held = true; if let Some(waker) = shared.waker.take() { waker.wake(); } @@ -687,8 +692,8 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u std::task::Poll::Pending }), ); - std::future::poll_fn(move |cx| { - let mut shared = shared.borrow_mut(); + let step = std::future::poll_fn(|cx| { + let mut shared = waiter.0.borrow_mut(); match shared.step { Some(step) => std::task::Poll::Ready(step), None => { @@ -697,7 +702,9 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u } } }) - .await + .await; + drop(waiter); + step } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -720,7 +727,7 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { - if HELD.get() { + if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); return std::task::Poll::Pending; diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 6d1f2ca..061f8e7 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -47,7 +47,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 1usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -88,7 +88,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 2usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -129,7 +129,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { 3usize => { let res = { let __runs = u.int_in_range(0..=3u8).unwrap() as u32; - let __at = proxy::conversion::executor::arrive_after( + let __at = proxy::conversion::executor::step_after( proxy::conversion::executor::current_step() + __runs, ) .await; @@ -331,6 +331,30 @@ trait Fuzz: Sized { #[allow(dead_code)] fn consume(self, _u: &mut Unstructured<'_>) {} } +thread_local! { + static FUZZ_SEED : u64 = { let random = proxy::util::debug::get_random(); + u64::from_le_bytes(random[..8].try_into().unwrap()) }; +} +/// Fuzz data drawn from `text`, e.g. a call's arguments: the same for the same text in +/// a run, so that the host answers alike, and different in another run. The text's +/// hash seeds splitmix64, which makes as much data as `get-random` gives. +#[allow(dead_code)] +fn fuzz_data(text: &str) -> Vec { + use std::hash::{Hash, Hasher}; + let mut hasher = std::hash::DefaultHasher::new(); + FUZZ_SEED.with(|seed| seed.hash(&mut hasher)); + text.hash(&mut hasher); + let mut state = hasher.finish(); + (0..128) + .flat_map(|_| { + state = state.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + (z ^ (z >> 31)).to_le_bytes() + }) + .collect() +} impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 9b0ecdf..6c19002 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -39,8 +39,8 @@ impl exports::proxy::conversion::executor::Guest for Stub { async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } - async fn arrive_after(step: u32) -> u32 { - arrive_after(step).await + async fn step_after(step: u32) -> u32 { + step_after(step).await } fn host_drops(dropping: bool) -> () { MOVING.with(|moving| moving.set(dropping)) @@ -84,8 +84,8 @@ impl exports::test::p3::host::Guest for Stub { } else { None }; - let __buf = __params.join(",") + "test:p3/host@0.1.0.fetch"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data(&(__params.join(",") + "test:p3/host@0.1.0.fetch")); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -218,8 +218,8 @@ impl exports::test::p3::host::GuestBody for MockedResource { Fuzz::consume(contents, &mut __host); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "new test:p3/host/body@0.1.0"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data(&(__params.join(",") + "new test:p3/host/body@0.1.0")); + let mut u = Unstructured::new(&__data); let res = MockedResource { name: "body".to_string(), ..Fuzz::fuzz(&mut u).unwrap() @@ -265,8 +265,8 @@ impl exports::test::p3::host::GuestBody for MockedResource { } else { None }; - let __buf = __params.join(",") + "test:p3/host/body@0.1.0.size"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data(&(__params.join(",") + "test:p3/host/body@0.1.0.size")); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -307,8 +307,10 @@ impl exports::test::p3::host::GuestBody for MockedResource { drop((this,)); MOVING.with(|moving| moving.set(false)); let __at: Option = None; - let __buf = __params.join(",") + "test:p3/host/body@0.1.0.consume"; - let mut u = Unstructured::new(&__buf.as_bytes()); + let __data = fuzz_data( + &(__params.join(",") + "test:p3/host/body@0.1.0.consume"), + ); + let mut u = Unstructured::new(&__data); let res = Fuzz::fuzz(&mut u).unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); proxy::recorder::record::record( @@ -480,6 +482,30 @@ trait Fuzz: Sized { #[allow(dead_code)] fn consume(self, _u: &mut Unstructured<'_>) {} } +thread_local! { + static FUZZ_SEED : u64 = { let random = proxy::util::debug::get_random(); + u64::from_le_bytes(random[..8].try_into().unwrap()) }; +} +/// Fuzz data drawn from `text`, e.g. a call's arguments: the same for the same text in +/// a run, so that the host answers alike, and different in another run. The text's +/// hash seeds splitmix64, which makes as much data as `get-random` gives. +#[allow(dead_code)] +fn fuzz_data(text: &str) -> Vec { + use std::hash::{Hash, Hasher}; + let mut hasher = std::hash::DefaultHasher::new(); + FUZZ_SEED.with(|seed| seed.hash(&mut hasher)); + text.hash(&mut hasher); + let mut state = hasher.finish(); + (0..128) + .flat_map(|_| { + state = state.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + (z ^ (z >> 31)).to_le_bytes() + }) + .collect() +} impl Fuzz for MockedResource { fn fuzz(_u: &mut Unstructured<'_>) -> Result { Ok(Self { @@ -864,8 +890,8 @@ thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = - const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < bool > = const - { std::cell::Cell::new(false) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const + { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -884,16 +910,13 @@ fn current_step() -> u32 { #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { let mut left = runs; - wait_for_run( - move || { - if left == 0 { - return true; - } - left -= 1; - false - }, - false, - ) + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) .await } /// Waits, from any task, until a run of the executor in `step` or later, or returns at once if @@ -904,23 +927,15 @@ async fn wait_until_step(step: u32) { if current_step() >= step { return; } - wait_for_run(move || current_step() >= step, false).await; + wait_for_run(move || current_step() >= step).await; } /// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line -/// `start` makes, e.g. an export call's arrival, which it returns. The executor doesn't run in -/// between, e.g. for a guest's event, so that the step follows the run it waited for, which -/// replay waits for from the step alone. +/// `start` makes, e.g. an export call's arrival, which it returns: the one after the run it +/// waited for, as the executor doesn't run in between (see `wait_for_run`). #[allow(dead_code)] -async fn arrive_after(step: u32) -> u32 { - if current_step() < step { - wait_for_run(move || current_step() >= step, true).await; - } - HELD.set(false); - let step = next_step(); - if WOKEN_WHILE_HELD.replace(false) { - wake_executor(); - } - step +async fn step_after(step: u32) -> u32 { + wait_until_step(step).await; + next_step() } /// Waits, in a job of the executor, until its run in `step`, waking the executor itself until /// then, e.g. replay's job for a copy recorded in that step. @@ -943,24 +958,40 @@ async fn reach_step(step: u32) { .await } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job -/// asks in each run, waking the executor itself in between, as no host event would. With `hold`, -/// the executor doesn't run again until `arrive_after` releases it. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u32 { +/// asks in each run, waking the executor itself in between, as no host event would. +/// +/// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so +/// that what the task does next, e.g. return a STARTED call, or take an export call's step, +/// follows the run it waited for, in record and replay alike: replay knows only the step. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, + held: bool, } - let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_shared = shared.clone(); + /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. + /// the mock of an import the guest cancelled. + struct Waiter(std::rc::Rc>); + impl Drop for Waiter { + fn drop(&mut self) { + if std::mem::take(&mut self.0.borrow_mut().held) { + HELD.set(HELD.get() - 1); + if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + } + } + } + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job_shared = waiter.0.clone(); spawn( std::future::poll_fn(move |_| { if done() { - if hold { - HELD.set(true); - } + HELD.set(HELD.get() + 1); let mut shared = job_shared.borrow_mut(); shared.step = Some(current_step()); + shared.held = true; if let Some(waker) = shared.waker.take() { waker.wake(); } @@ -970,8 +1001,8 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u std::task::Poll::Pending }), ); - std::future::poll_fn(move |cx| { - let mut shared = shared.borrow_mut(); + let step = std::future::poll_fn(|cx| { + let mut shared = waiter.0.borrow_mut(); match shared.step { Some(step) => std::task::Poll::Ready(step), None => { @@ -980,7 +1011,9 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u } } }) - .await + .await; + drop(waiter); + step } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -1003,7 +1036,7 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { - if HELD.get() { + if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); return std::task::Poll::Pending; diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index f463e62..8d607d6 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -41,7 +41,7 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); while let Some(line) = proxy::recorder::replay::next_host_line() { if let Some(at) = line.at { - let step = proxy::conversion::executor::arrive_after(at - 1).await; + let step = proxy::conversion::executor::step_after(at - 1).await; assert_eq!(step, at, "replay came to {:?} in step {step}", line.event); } match line.event { diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index b4d968d..68fab7a 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -38,8 +38,8 @@ impl exports::proxy::conversion::executor::Guest for Stub { async fn wait_until_step(step: u32) -> () { wait_until_step(step).await } - async fn arrive_after(step: u32) -> u32 { - arrive_after(step).await + async fn step_after(step: u32) -> u32 { + step_after(step).await } fn host_drops(dropping: bool) -> () { MOVING.with(|moving| moving.set(dropping)) @@ -764,8 +764,8 @@ thread_local! { static EXECUTOR : std::cell::RefCell < Executor > = std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = - const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < bool > = const - { std::cell::Cell::new(false) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const + { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -784,16 +784,13 @@ fn current_step() -> u32 { #[allow(dead_code)] async fn wait_for_runs(runs: u32) -> u32 { let mut left = runs; - wait_for_run( - move || { - if left == 0 { - return true; - } - left -= 1; - false - }, - false, - ) + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) .await } /// Waits, from any task, until a run of the executor in `step` or later, or returns at once if @@ -804,23 +801,15 @@ async fn wait_until_step(step: u32) { if current_step() >= step { return; } - wait_for_run(move || current_step() >= step, false).await; + wait_for_run(move || current_step() >= step).await; } /// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line -/// `start` makes, e.g. an export call's arrival, which it returns. The executor doesn't run in -/// between, e.g. for a guest's event, so that the step follows the run it waited for, which -/// replay waits for from the step alone. +/// `start` makes, e.g. an export call's arrival, which it returns: the one after the run it +/// waited for, as the executor doesn't run in between (see `wait_for_run`). #[allow(dead_code)] -async fn arrive_after(step: u32) -> u32 { - if current_step() < step { - wait_for_run(move || current_step() >= step, true).await; - } - HELD.set(false); - let step = next_step(); - if WOKEN_WHILE_HELD.replace(false) { - wake_executor(); - } - step +async fn step_after(step: u32) -> u32 { + wait_until_step(step).await; + next_step() } /// Waits, in a job of the executor, until its run in `step`, waking the executor itself until /// then, e.g. replay's job for a copy recorded in that step. @@ -843,24 +832,40 @@ async fn reach_step(step: u32) { .await } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job -/// asks in each run, waking the executor itself in between, as no host event would. With `hold`, -/// the executor doesn't run again until `arrive_after` releases it. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u32 { +/// asks in each run, waking the executor itself in between, as no host event would. +/// +/// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so +/// that what the task does next, e.g. return a STARTED call, or take an export call's step, +/// follows the run it waited for, in record and replay alike: replay knows only the step. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { #[derive(Default)] struct Done { step: Option, waker: Option, + held: bool, + } + /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. + /// the mock of an import the guest cancelled. + struct Waiter(std::rc::Rc>); + impl Drop for Waiter { + fn drop(&mut self) { + if std::mem::take(&mut self.0.borrow_mut().held) { + HELD.set(HELD.get() - 1); + if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + } + } } - let shared = std::rc::Rc::new(std::cell::RefCell::new(Done::default())); - let job_shared = shared.clone(); + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job_shared = waiter.0.clone(); spawn( std::future::poll_fn(move |_| { if done() { - if hold { - HELD.set(true); - } + HELD.set(HELD.get() + 1); let mut shared = job_shared.borrow_mut(); shared.step = Some(current_step()); + shared.held = true; if let Some(waker) = shared.waker.take() { waker.wake(); } @@ -870,8 +875,8 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u std::task::Poll::Pending }), ); - std::future::poll_fn(move |cx| { - let mut shared = shared.borrow_mut(); + let step = std::future::poll_fn(|cx| { + let mut shared = waiter.0.borrow_mut(); match shared.step { Some(step) => std::task::Poll::Ready(step), None => { @@ -880,7 +885,9 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static, hold: bool) -> u } } }) - .await + .await; + drop(waiter); + step } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] @@ -903,7 +910,7 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { - if HELD.get() { + if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); return std::task::Poll::Pending; From 47201a289634e20f047821659e5944055eda07a0 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 12:08:24 -0800 Subject: [PATCH 18/25] add race fixture test --- Makefile | 14 +- docs/p3-record-replay.md | 43 +- tests/fixtures/spike-p3-race/.gitignore | 4 + tests/fixtures/spike-p3-race/Cargo.lock | 441 ++++++++++++++++++ tests/fixtures/spike-p3-race/Cargo.toml | 11 + tests/fixtures/spike-p3-race/Makefile | 17 + .../compose-record-component-host.wac | 10 + .../fixtures/spike-p3-race/compose-record.wac | 9 + .../fixtures/spike-p3-race/compose-replay.wac | 9 + .../fixtures/spike-p3-race/driver/Cargo.toml | 11 + .../fixtures/spike-p3-race/driver/src/lib.rs | 18 + tests/fixtures/spike-p3-race/guest/Cargo.toml | 11 + tests/fixtures/spike-p3-race/guest/src/lib.rs | 50 ++ tests/fixtures/spike-p3-race/host/Cargo.toml | 12 + tests/fixtures/spike-p3-race/host/src/lib.rs | 20 + tests/fixtures/spike-p3-race/proxy-common.rs | 97 ++++ .../spike-p3-race/proxy-record/Cargo.toml | 17 + .../spike-p3-race/proxy-record/src/lib.rs | 74 +++ .../spike-p3-race/proxy-replay/Cargo.toml | 12 + .../spike-p3-race/proxy-replay/src/lib.rs | 94 ++++ tests/fixtures/spike-p3-race/run.sh | 40 ++ tests/fixtures/spike-p3-race/wit/race.wit | 54 +++ 22 files changed, 1045 insertions(+), 23 deletions(-) create mode 100644 tests/fixtures/spike-p3-race/.gitignore create mode 100644 tests/fixtures/spike-p3-race/Cargo.lock create mode 100644 tests/fixtures/spike-p3-race/Cargo.toml create mode 100644 tests/fixtures/spike-p3-race/Makefile create mode 100644 tests/fixtures/spike-p3-race/compose-record-component-host.wac create mode 100644 tests/fixtures/spike-p3-race/compose-record.wac create mode 100644 tests/fixtures/spike-p3-race/compose-replay.wac create mode 100644 tests/fixtures/spike-p3-race/driver/Cargo.toml create mode 100644 tests/fixtures/spike-p3-race/driver/src/lib.rs create mode 100644 tests/fixtures/spike-p3-race/guest/Cargo.toml create mode 100644 tests/fixtures/spike-p3-race/guest/src/lib.rs create mode 100644 tests/fixtures/spike-p3-race/host/Cargo.toml create mode 100644 tests/fixtures/spike-p3-race/host/src/lib.rs create mode 100644 tests/fixtures/spike-p3-race/proxy-common.rs create mode 100644 tests/fixtures/spike-p3-race/proxy-record/Cargo.toml create mode 100644 tests/fixtures/spike-p3-race/proxy-record/src/lib.rs create mode 100644 tests/fixtures/spike-p3-race/proxy-replay/Cargo.toml create mode 100644 tests/fixtures/spike-p3-race/proxy-replay/src/lib.rs create mode 100755 tests/fixtures/spike-p3-race/run.sh create mode 100644 tests/fixtures/spike-p3-race/wit/race.wit diff --git a/Makefile b/Makefile index 9cc7636..9f63894 100644 --- a/Makefile +++ b/Makefile @@ -76,15 +76,23 @@ test-fixture: $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm target/release/proxy-component run composed.wasm --invoke 'start()' --trace $(FIXTURE_OUT)/trace.out -# Fuzzes the main components of tests/fixtures/proxy-run/guest and of the p3 fixture, and -# replays each trace it writes, which ends as the fuzzing run did: to the end, or in the main -# component's trap, e.g. a guest's panic on a fuzzed error. +# The spike's guest, which joins two concurrent results in the order its wakeups receive them +RACE = tests/fixtures/spike-p3-race +RACE_OUT = target/fixture-race +RACE_WASM = $(RACE_OUT)/wasm32-wasip2/release/race_guest.wasm +# Fuzzes the main components of tests/fixtures/proxy-run/guest, the p3 fixture, wasi_http_p3 and +# the spike's guest, and replays each trace it writes, which ends as the fuzzing run did: to the +# end, or in the main component's trap, e.g. a guest's panic on a fuzzed error. test-fuzz-replay: cargo build --manifest-path $(FIXTURE)/guest/Cargo.toml --target wasm32-wasip2 --release --target-dir $(FIXTURE_OUT) $(MAKE) run-fuzz-replay WASM=$(FIXTURE_WASM)/proxy_guest.wasm OUT=$(FIXTURE_OUT) cargo build --manifest-path $(P3_GUEST)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_GUEST_OUT) $(MAKE) run-fuzz-replay WASM=$(P3_GUEST_WASM) OUT=$(P3_GUEST_OUT) WASMTIME_FLAGS="$(WASMTIME_FLAGS) -W component-model-error-context=y" + $(MAKE) run-fuzz-replay WASM=tests/wasi_http_p3.wasm OUT=target/fixture-http-p3 + cargo build --manifest-path $(RACE)/Cargo.toml -p race-guest --target wasm32-wasip2 --release --target-dir $(RACE_OUT) + $(MAKE) run-fuzz-replay WASM=$(RACE_WASM) OUT=$(RACE_OUT) run-fuzz-replay: + mkdir -p $(OUT) $(INSTRUMENT) -m fuzz $(WASM) cp composed.wasm $(OUT)/fuzz.wasm $(INSTRUMENT) -m replay $(WASM) diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 6bc7e17..db41f94 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -267,8 +267,9 @@ Limits: replay's jobs process the lines in. A host line can come after the guest acted on a handle the line brings in, as the host records a copy once its job sees it complete: e.g. a future's value with a mock in it, which the guest took and dropped first. So a `Drop` or a `Value` of a label - no line has mentioned yet ends the binding of the line that brings it in. Replay's labels are its own numbers, which it reuses too: a - replay label is paired again once the trace's label it was paired with has ended. A stream or + no line has mentioned yet ends the binding of the line that brings it in. Replay's labels are + its own numbers, which it reuses too: a replay label is paired again once the trace's label it + was paired with has ended. A stream or future replay makes, from a value the trace hands out, first appears in a host line, which replay doesn't check, so replay pairs its label explicitly, with the binding after that line: the end the host keeps. The lines replay performs it renames to its own labels, e.g. the @@ -376,9 +377,10 @@ Replay has to know when to perform each host line. There are three kinds of line own. On each arrival, the exports component calls `step` on `proxy:conversion/executor`, a sync function that increments the counter and returns it, as the export `Call`'s `at`; the executor's later runs are numbered after it. In replay, `start` makes an export call recorded at step `n` once -the count reaches `n - 1`, by calling `step` and then the export. The wait and the step are one call, `step-after`, which takes the step in the imports -component's task as soon as its wait resumes, while the executor is still held, as above. As two -calls, the executor could run between the wait's return and `start`'s call of `step`. An arrival can't fall within a run of the +the count reaches `n - 1`, by calling `step` and then the export. The wait and the step are one +call, `step-after`, which takes the step in the imports component's task as soon as its wait +resumes, while the executor is still held, as above. As two calls, the executor could run between +the wait's return and `start`'s call of `step`. An arrival can't fall within a run of the executor: the host only calls an export while no wasm is running, unless something suspends where the proxy can't see it, which the requirements below rule out. The host's drops of the guest's resources, which `start` makes too, take a step the same way. @@ -514,25 +516,25 @@ or blocks first. ## Spike results -[`tests/spike-p3-race`](../tests/spike-p3-race) tests steps in wasmtime 49, with +[`tests/fixtures/spike-p3-race`](../tests/fixtures/spike-p3-race) tests steps in wasmtime 49, with hand-written components: a guest whose `run` calls `fetch("a")` and `fetch("b")` concurrently and joins the results in the order its wakeups receive them; a record proxy whose `fetch` forwards to wasmtime's `wait-for`, with delays from `DELAY_A` and `DELAY_B` or random, and whose executor counts its steps; a replay proxy that delivers each result in its recorded step; and a driver -that runs the executor alongside the guest. `make -C tests/spike-p3-race` records each scenario, -replays it, and requires the same result, the same trace, and the same results in each of the -guest's wakeups. +that runs the executor alongside the guest. `make -C tests/fixtures/spike-p3-race` records each +scenario, replays it, and requires the same result, the same trace, and the same results in each +of the guest's wakeups. - **The clock works.** All scenarios replay exactly: each result first (`"AB"`, `"BA"`), each call RETURNED, both RETURNED, the guest busy until both results are ready, and random delays. - **RETURNED needs the host to be host functions.** With the host as a component instead - (`make -C tests/spike-p3-race component-host`), a call the proxy saw RETURNED reached the guest - as STARTED, and the guest got the two results in separate wakeups, while replay gave them - together. wasmtime runs the proxy's call into a component as a queued work item, which suspends - the proxy while it is entered, and holds back the guest's next call into it (`do_not_enter`, - `concurrent.rs:774`). Calling a host component in replay as well made other scenarios diverge, - as a component host suspends differently in record and replay. Removing it, i.e. forwarding to - host functions, fixed it. + (`make -C tests/fixtures/spike-p3-race component-host`), a call the proxy saw RETURNED reached + the guest as STARTED, and the guest got the two results in separate wakeups, while replay gave + them together. wasmtime runs the proxy's call into a component as a queued work item, which + suspends the proxy while it is entered, and holds back the guest's next call into it + (`do_not_enter`, `concurrent.rs:774`). Calling a host component in replay as well made other + scenarios diverge, as a component host suspends differently in record and replay. Removing it, + i.e. forwarding to host functions, fixed it. - **Sync WASI 0.2 calls change the schedule.** With the proxy and the guest printing each line with `println!`, several scenarios diverged; buffering the output fixed them. - **Two results never reached the guest in one wakeup**, through the proxy, nor, in one-off @@ -623,10 +625,11 @@ shim](#portability) could provide; see No guest task on import calls, in as guest-internal streams, or what the guest's waits returned, which the shim could record. - **A fixpoint test**: record a run, replay it while recording, and require the two traces to be equal. The spike does this, for its scenarios. -- **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2 and p3 fixtures, and - replays each trace, which has to end as the fuzzing run did: to the end, or in the guest's trap, - e.g. a panic on an error the fuzzed host returned, which is what fuzzing finds. A trap in a - generated component or the recorder, e.g. a divergence, fails it (`tests/run-wasm.sh`). +- **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2 and p3 fixtures, + `wasi_http_p3`, and the spike's guest, and replays each trace, which has to end as the fuzzing + run did: to the end, or in the guest's trap, e.g. a panic on an error the fuzzed host returned, + which is what fuzzing finds. A trap in a generated component or the recorder, e.g. a + divergence, fails it (`tests/run-wasm.sh`). - **Enumerated scenarios**: for small guests, every placement of host completions across steps, rather than random delays, and guests that merge nondeterministically, which are the ones steps exist for. diff --git a/tests/fixtures/spike-p3-race/.gitignore b/tests/fixtures/spike-p3-race/.gitignore new file mode 100644 index 0000000..dbc9e0f --- /dev/null +++ b/tests/fixtures/spike-p3-race/.gitignore @@ -0,0 +1,4 @@ +/target +/record.wasm +/replay.wasm +/record-component-host.wasm diff --git a/tests/fixtures/spike-p3-race/Cargo.lock b/tests/fixtures/spike-p3-race/Cargo.lock new file mode 100644 index 0000000..3029c7d --- /dev/null +++ b/tests/fixtures/spike-p3-race/Cargo.lock @@ -0,0 +1,441 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "macro-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "race-driver" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "race-guest" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "race-host" +version = "0.1.0" +dependencies = [ + "wasip3", + "wit-bindgen", +] + +[[package]] +name = "race-proxy-record" +version = "0.1.0" +dependencies = [ + "futures-channel", + "wasip3", + "wit-bindgen", +] + +[[package]] +name = "race-proxy-replay" +version = "0.1.0" +dependencies = [ + "futures-channel", + "wit-bindgen", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "wasip3" +version = "0.10.0+wasi-0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1657e9f48c63d6c3f143f70d5c6e2be7d463371a7af1ed939a5b83d3ca080d0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4495a6f85dd94c411c749303efcbc5defade0b0cbdbc0541495241ab917a9b5a" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86ff6668190d9fb70372b306b4f9fe8930f0e2f57ce72e4922bf2f117900949" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974d643b125b272607e2fff8012ea14a8328acd7621e4bafa7afb32500052b1b" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06" +dependencies = [ + "bitflags", + "futures", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e122e390603abfacd548027016cd0113a6e2bf2f8ce6bdd3658b013456ea8" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e26e13b9d972026778d2ffb7985736de116026fdc8d9464f966e12df2eb240" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a008a0d6b4840677326beb4edbeb210a2f1b7d0183fb7cc33f466b90fd7fb6" +dependencies = [ + "anyhow", + "macro-string", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078def49fdb49cab405c3006e05793b3fdaf7069126ba6b9c97d01ec8c98fe4c" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f0dc051466ffe46787721fca3d088536f158ae400be044f1b1c386ba9397ad" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/tests/fixtures/spike-p3-race/Cargo.toml b/tests/fixtures/spike-p3-race/Cargo.toml new file mode 100644 index 0000000..1f5d60f --- /dev/null +++ b/tests/fixtures/spike-p3-race/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +members = ["guest", "host", "proxy-record", "proxy-replay", "driver"] +resolver = "2" + +[workspace.dependencies] +wit-bindgen = { version = "0.63.0", features = ["async-spawn", "inter-task-wakeup"] } +futures-channel = "0.3" + +[profile.release] +codegen-units = 1 +opt-level = "s" diff --git a/tests/fixtures/spike-p3-race/Makefile b/tests/fixtures/spike-p3-race/Makefile new file mode 100644 index 0000000..99c19b8 --- /dev/null +++ b/tests/fixtures/spike-p3-race/Makefile @@ -0,0 +1,17 @@ +# The spike of docs/p3-record-replay.md. `make` builds, composes, and runs ./run.sh, which records +# each scenario and checks that its replay matches. `make component-host` records with the host as +# another component instead of wasmtime's own host functions, which replay can't reproduce. +R = target/wasm32-wasip2/release +RC = target/component-host/wasm32-wasip2/release +.PHONY: all build compose component-host +all: compose + ./run.sh 20 +build: + cargo build --release --target wasm32-wasip2 + cargo build --release --target wasm32-wasip2 -p race-proxy-record --features component-host --target-dir target/component-host +compose: build + wac compose compose-record.wac --dep spike:proxy=$(R)/race_proxy_record.wasm --dep spike:guest=$(R)/race_guest.wasm --dep spike:driver=$(R)/race_driver.wasm -o record.wasm + wac compose compose-record-component-host.wac --dep spike:host=$(R)/race_host.wasm --dep spike:proxy=$(RC)/race_proxy_record.wasm --dep spike:guest=$(R)/race_guest.wasm --dep spike:driver=$(R)/race_driver.wasm -o record-component-host.wasm + wac compose compose-replay.wac --dep spike:proxy=$(R)/race_proxy_replay.wasm --dep spike:guest=$(R)/race_guest.wasm --dep spike:driver=$(R)/race_driver.wasm -o replay.wasm +component-host: compose + RECORD=record-component-host.wasm OUT=target/runs-component-host ./run.sh 20 diff --git a/tests/fixtures/spike-p3-race/compose-record-component-host.wac b/tests/fixtures/spike-p3-race/compose-record-component-host.wac new file mode 100644 index 0000000..3cfdcee --- /dev/null +++ b/tests/fixtures/spike-p3-race/compose-record-component-host.wac @@ -0,0 +1,10 @@ +package spike:record-component-host; +let host = new spike:host { ... }; +let proxy = new spike:proxy { "spike:race/upstream": host["spike:race/host"], ... }; +let guest = new spike:guest { "spike:race/host": proxy["spike:race/host"], ... }; +let driver = new spike:driver { + "spike:race/executor": proxy["spike:race/executor"], + "spike:race/guest": guest["spike:race/guest"], + ... +}; +export driver...; diff --git a/tests/fixtures/spike-p3-race/compose-record.wac b/tests/fixtures/spike-p3-race/compose-record.wac new file mode 100644 index 0000000..3db0361 --- /dev/null +++ b/tests/fixtures/spike-p3-race/compose-record.wac @@ -0,0 +1,9 @@ +package spike:record; +let proxy = new spike:proxy { ... }; +let guest = new spike:guest { "spike:race/host": proxy["spike:race/host"], ... }; +let driver = new spike:driver { + "spike:race/executor": proxy["spike:race/executor"], + "spike:race/guest": guest["spike:race/guest"], + ... +}; +export driver...; diff --git a/tests/fixtures/spike-p3-race/compose-replay.wac b/tests/fixtures/spike-p3-race/compose-replay.wac new file mode 100644 index 0000000..1a10ee4 --- /dev/null +++ b/tests/fixtures/spike-p3-race/compose-replay.wac @@ -0,0 +1,9 @@ +package spike:replay; +let proxy = new spike:proxy { ... }; +let guest = new spike:guest { "spike:race/host": proxy["spike:race/host"], ... }; +let driver = new spike:driver { + "spike:race/executor": proxy["spike:race/executor"], + "spike:race/guest": guest["spike:race/guest"], + ... +}; +export driver...; diff --git a/tests/fixtures/spike-p3-race/driver/Cargo.toml b/tests/fixtures/spike-p3-race/driver/Cargo.toml new file mode 100644 index 0000000..e1b2286 --- /dev/null +++ b/tests/fixtures/spike-p3-race/driver/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "race-driver" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen.workspace = true diff --git a/tests/fixtures/spike-p3-race/driver/src/lib.rs b/tests/fixtures/spike-p3-race/driver/src/lib.rs new file mode 100644 index 0000000..ba8842a --- /dev/null +++ b/tests/fixtures/spike-p3-race/driver/src/lib.rs @@ -0,0 +1,18 @@ +//! Runs the proxy's executor alongside the guest's `run`, as the generated `start` does. +wit_bindgen::generate!({ path: "../wit", world: "driver" }); + +use spike::race::{executor, guest}; + +struct Driver; + +impl exports::spike::race::guest::Guest for Driver { + async fn run() -> String { + let executor = wit_bindgen::spawn_local(executor::run()); + let out = guest::run().await; + executor::stop(); + executor.await; + out + } +} + +export!(Driver); diff --git a/tests/fixtures/spike-p3-race/guest/Cargo.toml b/tests/fixtures/spike-p3-race/guest/Cargo.toml new file mode 100644 index 0000000..734166d --- /dev/null +++ b/tests/fixtures/spike-p3-race/guest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "race-guest" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen.workspace = true diff --git a/tests/fixtures/spike-p3-race/guest/src/lib.rs b/tests/fixtures/spike-p3-race/guest/src/lib.rs new file mode 100644 index 0000000..76d739c --- /dev/null +++ b/tests/fixtures/spike-p3-race/guest/src/lib.rs @@ -0,0 +1,50 @@ +//! `p3-race` from component-ui's docs/p3.md: fetches "a" and "b" concurrently, and joins the +//! results in the order they complete. +wit_bindgen::generate!({ path: "../wit", world: "race-guest" }); + +use spike::race::host::fetch; +use std::future::Future; +use std::task::Poll; + +struct Guest; + +impl exports::spike::race::guest::Guest for Guest { + async fn run() -> String { + let mut a = Box::pin(fetch("a".to_string())); + let mut b = Box::pin(fetch("b".to_string())); + let (mut a_done, mut b_done) = (false, false); + let mut out = String::new(); + // What each wakeup received, returned with the result rather than printed: printing is a + // sync call to the host, which can suspend the guest and change the schedule. + let mut wakeups = Vec::new(); + // `BUSY_MS`: how long to keep running after starting both calls, so that both results + // can arrive before the guest next waits. + let mut busy = std::env::var("BUSY_MS").ok().and_then(|ms| ms.parse::().ok()); + // Both are polled on each wakeup, `a` first: if both are ready, `a` comes first. + std::future::poll_fn(|cx| { + // What this wakeup received, which the replay must see alike. + let mut got = String::new(); + if !a_done && let Poll::Ready(value) = a.as_mut().poll(cx) { + got += &value; + a_done = true; + } + if !b_done && let Poll::Ready(value) = b.as_mut().poll(cx) { + got += &value; + b_done = true; + } + if !got.is_empty() { + wakeups.push(got.clone()); + } + out += &got; + if let Some(ms) = busy.take() { + let start = std::time::Instant::now(); + while start.elapsed() < std::time::Duration::from_millis(ms) {} + } + if a_done && b_done { Poll::Ready(()) } else { Poll::Pending } + }) + .await; + format!("{out} (wakeups: {})", wakeups.join(" ")) + } +} + +export!(Guest); diff --git a/tests/fixtures/spike-p3-race/host/Cargo.toml b/tests/fixtures/spike-p3-race/host/Cargo.toml new file mode 100644 index 0000000..d439523 --- /dev/null +++ b/tests/fixtures/spike-p3-race/host/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "race-host" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen.workspace = true +wasip3 = "0.10.0" diff --git a/tests/fixtures/spike-p3-race/host/src/lib.rs b/tests/fixtures/spike-p3-race/host/src/lib.rs new file mode 100644 index 0000000..5f27e53 --- /dev/null +++ b/tests/fixtures/spike-p3-race/host/src/lib.rs @@ -0,0 +1,20 @@ +//! `fetch(key)` waits `DELAY_` milliseconds, or a random 0-19 if unset, then returns `key` in +//! uppercase. A delay of 0 returns at once, without awaiting anything. +wit_bindgen::generate!({ path: "../wit", world: "race-host" }); + +struct Host; + +impl exports::spike::race::host::Guest for Host { + async fn fetch(key: String) -> String { + let delay = std::env::var(format!("DELAY_{}", key.to_uppercase())) + .ok() + .and_then(|delay| delay.parse::().ok()) + .unwrap_or_else(|| u64::from(wasip3::random::random::get_random_bytes(1)[0] % 20)); + if delay > 0 { + wasip3::clocks::monotonic_clock::wait_for(delay * 1_000_000).await; + } + key.to_uppercase() + } +} + +export!(Host); diff --git a/tests/fixtures/spike-p3-race/proxy-common.rs b/tests/fixtures/spike-p3-race/proxy-common.rs new file mode 100644 index 0000000..251c85f --- /dev/null +++ b/tests/fixtures/spike-p3-race/proxy-common.rs @@ -0,0 +1,97 @@ +// What the record and replay proxies share: the executor of `assets/executor.rs`, which counts its +// wakeups, the clock that positions host events, and the trace. +// +// Trace lines, printed to stdout with a `trace: ` prefix: +// call the guest called `fetch(key)` +// returned the host returned at once, so the guest saw RETURNED +// started the host blocked, so the guest got a subtask +// deliver the host's result reached the executor at its wakeup `n` +use std::cell::{Cell, RefCell}; +use std::future::Future; +use std::pin::Pin; +use std::task::{Poll, Waker}; + +type Job = Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR: RefCell = RefCell::new(Executor::default()); + // The number of times the executor has woken up. + static WAKEUP: Cell = const { Cell::new(0) }; + // Set by a job waiting for a later wakeup, in replay, so that the executor wakes itself, as a + // host completion woke it in the recording. + static WAKE_AGAIN: Cell = const { Cell::new(false) }; +} + +thread_local! { + // The trace, printed by `stop_executor` at the end: printing is a sync call to the host, which + // can suspend the instance while it is entered, and so change the schedule. + static TRACE: RefCell> = const { RefCell::new(Vec::new()) }; +} +pub fn trace(line: String) { + TRACE.with_borrow_mut(|trace| trace.push(line)); +} + +pub fn wakeup() -> u32 { + WAKEUP.get() +} + +#[allow(dead_code)] +pub fn wake_again() { + WAKE_AGAIN.set(true); +} + +pub fn spawn(job: impl Future + 'static) { + EXECUTOR.with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} + +pub async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + WAKEUP.set(WAKEUP.get() + 1); + WAKE_AGAIN.set(false); + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR.with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR.with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + // Yields, and comes back as the next wakeup. + cx.waker().wake_by_ref(); + } + Poll::Pending + } + }) + }) + .await +} + +pub fn stop_executor() { + for line in TRACE.with_borrow_mut(std::mem::take) { + println!("trace: {line}"); + } + EXECUTOR.with_borrow_mut(|e| { + e.stopped = true; + if let Some(waker) = e.waker.take() { + waker.wake(); + } + }); +} diff --git a/tests/fixtures/spike-p3-race/proxy-record/Cargo.toml b/tests/fixtures/spike-p3-race/proxy-record/Cargo.toml new file mode 100644 index 0000000..843fc9c --- /dev/null +++ b/tests/fixtures/spike-p3-race/proxy-record/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "race-proxy-record" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[features] +# Forwards to the `race-host` component instead of wasmtime's own host functions. +component-host = [] + +[dependencies] +wit-bindgen.workspace = true +wasip3 = "0.10.0" +futures-channel.workspace = true diff --git a/tests/fixtures/spike-p3-race/proxy-record/src/lib.rs b/tests/fixtures/spike-p3-race/proxy-record/src/lib.rs new file mode 100644 index 0000000..ceb1e59 --- /dev/null +++ b/tests/fixtures/spike-p3-race/proxy-record/src/lib.rs @@ -0,0 +1,74 @@ +//! Forwards the guest's `fetch` to the host, and records each host event with the executor's +//! wakeup that observed it. +//! +//! The host is wasmtime's own host functions, as in record mode: `fetch(key)` waits `DELAY_` +//! milliseconds, or a random 0-19, with `wait-for`. With the `component-host` feature, it is the +//! `race-host` component instead. wasmtime runs a call into another component as a queued work +//! item, which suspends the caller while it's still entered, so the guest sees a call the proxy +//! saw RETURNED as STARTED, and its next call into the proxy waits; replay can't reproduce that. +#[cfg(not(feature = "component-host"))] +wit_bindgen::generate!({ path: "../wit", world: "proxy-record" }); +#[cfg(feature = "component-host")] +wit_bindgen::generate!({ path: "../wit", world: "proxy-record-component-host" }); + +#[path = "../../proxy-common.rs"] +mod common; +use common::*; +use std::future::Future; +use std::task::Poll; + +#[cfg(feature = "component-host")] +async fn host_fetch(key: String) -> String { + spike::race::upstream::fetch(key).await +} +/// `fetch` on wasmtime's own host functions. WASI 0.3's environment, a sync function that +/// doesn't suspend, unlike WASI 0.2's. +#[cfg(not(feature = "component-host"))] +async fn host_fetch(key: String) -> String { + let name = format!("DELAY_{}", key.to_uppercase()); + let delay = wasip3::cli::environment::get_environment() + .into_iter() + .find(|(n, _)| *n == name) + .and_then(|(_, delay)| delay.parse::().ok()) + .unwrap_or_else(|| u64::from(wasip3::random::random::get_random_bytes(1)[0] % 20)); + if delay > 0 { + wasip3::clocks::monotonic_clock::wait_for(delay * 1_000_000).await; + } + key.to_uppercase() +} + +struct Proxy; + +impl exports::spike::race::host::Guest for Proxy { + async fn fetch(key: String) -> String { + trace(format!("call {key}")); + let mut call = Box::pin(host_fetch(key.clone())); + // If the host's result is ready on the first poll, this export returns before it blocks, + // so the guest sees RETURNED. + if let Poll::Ready(value) = std::future::poll_fn(|cx| Poll::Ready(call.as_mut().poll(cx))).await { + trace(format!("returned {key} {value}")); + return value; + } + trace(format!("started {key}")); + // The executor takes over the pending call: wit-bindgen moves its waitable to the + // executor's task when the executor polls it. + let (tx, rx) = futures_channel::oneshot::channel(); + spawn(async move { + let value = call.await; + trace(format!("deliver {} {key} {value}", wakeup())); + let _ = tx.send(value); + }); + rx.await.unwrap() + } +} + +impl exports::spike::race::executor::Guest for Proxy { + async fn run() { + run_executor().await + } + fn stop() { + stop_executor() + } +} + +export!(Proxy); diff --git a/tests/fixtures/spike-p3-race/proxy-replay/Cargo.toml b/tests/fixtures/spike-p3-race/proxy-replay/Cargo.toml new file mode 100644 index 0000000..e5cfbbb --- /dev/null +++ b/tests/fixtures/spike-p3-race/proxy-replay/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "race-proxy-replay" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen.workspace = true +futures-channel.workspace = true diff --git a/tests/fixtures/spike-p3-race/proxy-replay/src/lib.rs b/tests/fixtures/spike-p3-race/proxy-replay/src/lib.rs new file mode 100644 index 0000000..07cfcd2 --- /dev/null +++ b/tests/fixtures/spike-p3-race/proxy-replay/src/lib.rs @@ -0,0 +1,94 @@ +//! Replays the trace on stdin: checks each guest call against it, and delivers each host result +//! at the executor's wakeup the trace gives. Prints the trace it sees, to compare. +wit_bindgen::generate!({ path: "../wit", world: "proxy-replay" }); + +#[path = "../../proxy-common.rs"] +mod common; +use common::*; +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::task::Poll; + +/// The recorded trace: the lines of guest calls, in order, and each delivery, by key. +struct Expected { + calls: VecDeque, + deliveries: HashMap, +} +thread_local! { + static EXPECTED: RefCell> = const { RefCell::new(None) }; +} + +fn load() { + EXPECTED.with_borrow_mut(|expected| { + if expected.is_some() { + return; + } + let mut calls = VecDeque::new(); + let mut deliveries = HashMap::new(); + for line in std::io::stdin().lines() { + let line = line.unwrap(); + let Some(line) = line.strip_prefix("trace: ") else { + continue; + }; + let words: Vec<&str> = line.split(' ').collect(); + if words[0] == "deliver" { + let n = words[1].parse().unwrap(); + deliveries.insert(words[2].to_string(), (n, words[3].to_string())); + } else { + calls.push_back(line.to_string()); + } + } + *expected = Some(Expected { calls, deliveries }); + }); +} + +fn next_call() -> String { + EXPECTED.with_borrow_mut(|e| e.as_mut().unwrap().calls.pop_front().expect("the trace ended")) +} + +struct Proxy; + +impl exports::spike::race::host::Guest for Proxy { + async fn fetch(key: String) -> String { + load(); + trace(format!("call {key}")); + let expected = next_call(); + assert_eq!(expected, format!("call {key}"), "diverged"); + let next = next_call(); + if let Some(value) = next.strip_prefix(&format!("returned {key} ")) { + trace(format!("returned {key} {value}")); + return value.to_string(); + } + assert_eq!(next, format!("started {key}"), "diverged"); + trace(format!("started {key}")); + let (n, value) = EXPECTED + .with_borrow_mut(|e| e.as_mut().unwrap().deliveries.remove(&key)) + .expect("no delivery in the trace"); + let (tx, rx) = futures_channel::oneshot::channel(); + spawn(async move { + std::future::poll_fn(|_| { + if wakeup() >= n { + Poll::Ready(()) + } else { + wake_again(); + Poll::Pending + } + }) + .await; + trace(format!("deliver {} {key} {value}", wakeup())); + let _ = tx.send(value); + }); + rx.await.unwrap() + } +} + +impl exports::spike::race::executor::Guest for Proxy { + async fn run() { + run_executor().await + } + fn stop() { + stop_executor() + } +} + +export!(Proxy); diff --git a/tests/fixtures/spike-p3-race/run.sh b/tests/fixtures/spike-p3-race/run.sh new file mode 100755 index 0000000..40c9f52 --- /dev/null +++ b/tests/fixtures/spike-p3-race/run.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# Records each scenario, replays its trace, and checks that the replay sees the same result and +# the same trace, line by line. Usage: ./run.sh [random runs] +# RECORD=record-component-host.wasm records with the host as a component instead. +set -u +OUT=${OUT:-target/runs} +mkdir -p "$OUT" +fail=0 +check() { # name, then wasmtime --env flags + name=$1; shift + wasmtime run "$@" --invoke 'run()' "${RECORD:-record.wasm}" > "$OUT/$name.record" 2>&1 + wasmtime run --invoke 'run()' replay.wasm < "$OUT/$name.record" > "$OUT/$name.replay" 2>&1 + recorded=$(tail -1 "$OUT/$name.record"); replayed=$(tail -1 "$OUT/$name.replay") + # Whether the guest got both results in one wakeup. + together=$(grep -c 'wakeups: [A-Z][A-Z])' "$OUT/$name.record") + shape=$(grep -E '^trace: ' "$OUT/$name.record" | sed 's/^trace: //; s/ [A-Z]*$//' | tr '\n' ',' ) + if cmp -s "$OUT/$name.record" "$OUT/$name.replay"; then + echo "ok $name: $recorded$([ "$together" -gt 0 ] && echo ' (the guest got both at once)') [$shape]" + else + echo "FAIL $name: recorded $recorded, replayed $replayed" + diff "$OUT/$name.record" "$OUT/$name.replay" | head -10 + fail=1 + fi +} +check a-first --env DELAY_A=5 --env DELAY_B=15 +check b-first --env DELAY_A=15 --env DELAY_B=5 +check a-returned --env DELAY_A=0 --env DELAY_B=10 +check b-returned --env DELAY_A=10 --env DELAY_B=0 +check both-returned --env DELAY_A=0 --env DELAY_B=0 +check same-delay --env DELAY_A=5 --env DELAY_B=5 +# The guest stays busy until both results have arrived. +check busy-a-first --env DELAY_A=5 --env DELAY_B=10 --env BUSY_MS=40 +check busy-b-first --env DELAY_A=10 --env DELAY_B=5 --env BUSY_MS=40 +check busy-same --env DELAY_A=5 --env DELAY_B=5 --env BUSY_MS=40 +i=0 +while [ $i -lt "${1:-20}" ]; do + check random-$i + i=$((i + 1)) +done +exit $fail diff --git a/tests/fixtures/spike-p3-race/wit/race.wit b/tests/fixtures/spike-p3-race/wit/race.wit new file mode 100644 index 0000000..87d5fc3 --- /dev/null +++ b/tests/fixtures/spike-p3-race/wit/race.wit @@ -0,0 +1,54 @@ +// The spike of docs/p3-record-replay.md: whether replaying each host event at the wakeup of the +// proxy's executor that observed it reproduces a run. +package spike:race; + +interface host { + fetch: async func(key: string) -> string; +} + +// The host, as the proxy imports it, so that the proxy can import and export `fetch`. +interface upstream { + fetch: async func(key: string) -> string; +} + +interface guest { + run: async func() -> string; +} + +interface executor { + run: async func(); + stop: func(); +} + +world race-guest { + import host; + export guest; +} + +world race-host { + export host; +} + +// The record proxy whose host is another component, `race-host`, rather than wasmtime's own host +// functions; see `proxy-record`. +world proxy-record-component-host { + import upstream; + export host; + export executor; +} + +world proxy-record { + export host; + export executor; +} + +world proxy-replay { + export host; + export executor; +} + +world driver { + import executor; + import guest; + export guest; +} From 75f351f1d92e9ff515a4f88be0a7ae8b016ce5b3 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 12:13:09 -0800 Subject: [PATCH 19/25] report error trace on trap --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9f63894..27b4c4c 100644 --- a/Makefile +++ b/Makefile @@ -99,9 +99,13 @@ run-fuzz-replay: cp composed.wasm $(OUT)/replay.wasm for i in 1 2 3 4 5 6 7 8 9 10; do \ $(RUN_WASM) $(OUT)/fuzz-trace.out $(WASMTIME_FLAGS) --invoke 'start()' $(OUT)/fuzz.wasm > /dev/null; fuzz=$$?; \ - [ $$fuzz -ne 1 ] || exit 1; \ + if [ $$fuzz -eq 1 ]; then \ + cat $(OUT)/fuzz-trace.out; echo "fuzzing failed, with the output above, in $(OUT)/fuzz-trace.out"; exit 1; \ + fi; \ $(RUN_WASM) $(OUT)/replay.out $(WASMTIME_FLAGS) --invoke 'start()' $(OUT)/replay.wasm < $(OUT)/fuzz-trace.out > /dev/null; \ - [ $$? -eq $$fuzz ] || exit 1; \ + if [ $$? -ne $$fuzz ]; then \ + cat $(OUT)/replay.out; echo "replay failed, with the output above, in $(OUT)/replay.out, of the trace in $(OUT)/fuzz-trace.out"; exit 1; \ + fi; \ done test-dialog: From f88ad7df5e202917d8f1de167ba3cf8dacaf8625 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 13:27:26 -0800 Subject: [PATCH 20/25] Record async imports of p3 components - Imports component: an async import polls the host's call once. A ready result is returned at once (RETURNED). Otherwise the proxy records Started, and an executor job awaits the host's subtask. The job records the Return in the step of the run it completed in, and hands the result back. The export waits for it as replay's mock waits for its step, holding the executor until it resumes (host_result). - Executor: wait_for_job runs a future as a job and returns its value with its step; wait_for_run is built on it. The job may borrow the waiting task's arguments, e.g. a method's self. It is only polled while the task waits, and dropped with it if the guest cancels, which cancels the host's call too. - Exports component: every export call takes a step for its arrival. Record mode has no start, so async export calls run the executor while they are pending, and stop it when the last one returns; a call arriving meanwhile keeps it running (call_export_recorded, record mode's `run`). call_export and trace_guest move out of start, to be shared with record mode. - Every mode's worlds now have proxy:conversion/executor, record mode included. --- assets/executor.rs | 78 ++- docs/p3-record-replay.md | 21 +- src/ast.rs | 10 +- src/codegen/mod.rs | 144 +++-- src/codegen/record.rs | 36 +- tests/snapshots/dialog/record_exports/lib.rs | 1 + tests/snapshots/dialog/record_imports/lib.rs | 83 ++- tests/snapshots/fuzz/record_exports/lib.rs | 56 +- tests/snapshots/fuzz/record_imports/lib.rs | 83 ++- .../snapshots/p3-dialog/record_exports/lib.rs | 1 + .../snapshots/p3-dialog/record_imports/lib.rs | 83 ++- tests/snapshots/p3-fuzz/record_exports/lib.rs | 56 +- tests/snapshots/p3-fuzz/record_imports/lib.rs | 83 ++- tests/snapshots/record/record_exports/lib.rs | 179 +++++-- tests/snapshots/record/record_imports/lib.rs | 490 ++++++++++++++++-- tests/snapshots/record/wit/component.wit | 2 + tests/snapshots/replay/record_exports/lib.rs | 56 +- tests/snapshots/replay/record_imports/lib.rs | 83 ++- 18 files changed, 1166 insertions(+), 379 deletions(-) diff --git a/assets/executor.rs b/assets/executor.rs index 77ec873..75dfb4e 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -129,23 +129,43 @@ async fn reach_step(step: u32) { } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. /// /// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so /// that what the task does next, e.g. return a STARTED call, or take an export call's step, /// follows the run it waited for, in record and replay alike: replay knows only the step. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { - #[derive(Default)] - struct Done { - step: Option, +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>(job: impl std::future::Future + 'a) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, waker: Option, held: bool, } - /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. - /// the mock of an import the guest cancelled. - struct Waiter(std::rc::Rc>); - impl Drop for Waiter { + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { fn drop(&mut self) { - if std::mem::take(&mut self.0.borrow_mut().held) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { HELD.set(HELD.get() - 1); if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { wake_executor(); @@ -153,26 +173,34 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { } } } - let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job: Job<'a, T> = Box::pin(job); + // SAFETY: the job outlives `'a` only as an empty `Option`: the waiter, which `'a` outlives, + // drops it when it is dropped, and it is only polled before. + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { job: Some(job), value: None, waker: None, held: false }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); let job_shared = waiter.0.clone(); - spawn(std::future::poll_fn(move |_| { - if done() { - HELD.set(HELD.get() + 1); - let mut shared = job_shared.borrow_mut(); - shared.step = Some(current_step()); - shared.held = true; - if let Some(waker) = shared.waker.take() { - waker.wake(); - } + spawn(std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); } - WAKE_AGAIN.set(true); - std::task::Poll::Pending + std::task::Poll::Ready(()) })); - let step = std::future::poll_fn(|cx| { + let value = std::future::poll_fn(|cx| { let mut shared = waiter.0.borrow_mut(); - match shared.step { - Some(step) => std::task::Poll::Ready(step), + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), None => { shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending @@ -181,7 +209,7 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { }) .await; drop(waiter); - step + value } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index db41f94..e20be2c 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -26,7 +26,9 @@ dialog support for p3. writes it for p2 and p3 components, with [steps](#steps) and [labels](#labels), and record mode for p2 components. - **Replay** of p2 and p3 traces ([Replay](#replay)): calls and steps, RETURNED and STARTED, - streams, futures and error-contexts. Recording p3 components is the next phase. + streams, futures and error-contexts. +- **Record mode for p3**: async imports, RETURNED or STARTED, with their steps. Streams and + futures are next. ## Where nondeterminism comes from @@ -289,8 +291,16 @@ component. In record mode, the imports component routes every interaction with the host through one executor task, like fuzz mode's: -- **Async imports.** The executor's job awaits the host's subtask, and completes the proxy's - export call with the result (its `task.return`). +- **Async imports.** The proxy's export polls the host's call once: if the result is ready, it + returns it at once. Otherwise it records `Started`, and hands the call to an executor job, + which awaits the host's subtask and records the `Return` in the step of the run it completed + in, then gives the result back to the proxy's export, which returns it (its `task.return`). It + waits for the job as replay's mock waits for the step, holding the executor until it resumes + (see [Steps](#steps)). The job may borrow the guest's arguments, e.g. a method's `self`: if the + guest cancels the call, the job is dropped with it, which cancels the host's call too. +- **Running the executor.** There is no `start` in record mode, so the exports component runs + the executor while the host's calls of async exports are pending, and stops it when the last + one returns, which lets it finish its jobs; a call that comes meanwhile keeps it running. - **Streams and futures, by pumps.** The proxy doesn't pass a stream end through, as the copies would then go between the host and the guest without it. It keeps the end it received, creates a pair of its own, and passes on that pair's other end. A pump is the executor job between the @@ -690,7 +700,10 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla 3. **Replay**, tested on traces that fuzz mode writes, with the checks of [Correctness](#checking-the-implementation): done. It replays 100 of 100 fuzz traces of the p3 fixture, and 60 of 60 of `wasi_http_p3`. -4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. +4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. Async imports + are done: `make test-record-p3` records `tests/fixtures/proxy-p3-record` under `wasmtime run`, + with delays that make its calls RETURNED or STARTED, and replays each trace. Next are streams + and futures, through pumps. 5. **Portability across runtimes**: the profile proposal, or the shim. ## Open questions diff --git a/src/ast.rs b/src/ast.rs index 0f854f6..220265e 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -44,7 +44,7 @@ impl<'a> Opt<'a> { } } /// Whether the main component's exports are driven by the generated `start`, rather than - /// by the host, which also runs `proxy:conversion/executor`. + /// by the host. Either way, the exports component runs `proxy:conversion/executor`. fn has_start(&self) -> bool { matches!(self.mode, Mode::Replay | Mode::Fuzz | Mode::Dialog) } @@ -76,9 +76,7 @@ impl<'a> Opt<'a> { out.push_str("world imports {\n"); self.push_mode_imports(&mut out); out.push_str("export proxy:conversion/conversion;\n"); - if self.has_start() { - out.push_str("export proxy:conversion/executor;\n"); - } + out.push_str("export proxy:conversion/executor;\n"); for name in interface_names(resolve, &world.imports)? { // Don't virtualize util imports if name.starts_with("proxy:util/") { @@ -130,9 +128,7 @@ impl<'a> Opt<'a> { out.push_str("world exports {\n"); self.push_mode_imports(&mut out); out.push_str("import proxy:conversion/conversion;\n"); - if self.has_start() { - out.push_str("import proxy:conversion/executor;\n"); - } + out.push_str("import proxy:conversion/executor;\n"); for name in interface_names(resolve, &world.imports)? { out.push_str(&format!("import {name};\n")); } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index bd12c47..1e50063 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -103,6 +103,12 @@ pub fn generate( if matches!(mode, Mode::Replay | Mode::Fuzz) { state.output.extend(job_defs(mode, has_executor)); } + if matches!(has_executor, Some(HasExecutor::Imports)) { + state.output.extend(export_call_defs(mode)); + } + if matches!(mode, Mode::Record) { + state.output.extend(record_defs(has_executor)); + } if matches!(mode, Mode::Replay | Mode::Fuzz) { let defs = state.replay_defs(); state.output.extend(defs); @@ -364,8 +370,8 @@ impl State { } res } - /// Whether this component exports `proxy:conversion/executor` or imports it; `None` in record - /// mode, which has no executor. + /// Whether this component exports `proxy:conversion/executor` or imports it; `None` for + /// bindings without it, e.g. of the tests. fn has_executor(&self) -> Option { let import = EXECUTOR_MODULE.strip_prefix("exports::").unwrap(); self.module_paths @@ -391,43 +397,10 @@ impl State { Mode::Replay => quote! { proxy::recorder::replay::finish(); }, _ => quote! {}, }; - // What the export calls of fuzz and replay modes share (see `start_call`), so that they - // stay alike, as replay requires: fuzz mode records a guest line, which replay checks. - let trace_guest = match self.mode { - Mode::Fuzz => quote! { proxy::recorder::record::record(line); }, - Mode::Replay => quote! { proxy::recorder::replay::check(line); }, - _ => quote! {}, - }; - let start_defs = match self.mode { - Mode::Fuzz | Mode::Replay => quote! { - /// A line of the guest, which fuzz mode records, and replay checks. - #[allow(dead_code)] - fn trace_guest(line: &Line) { - #trace_guest - } - /// Calls an async export, and reports that the guest started it, if it blocks. - #[allow(dead_code)] - async fn call_export(id: TaskId, call: impl std::future::Future) -> T { - let mut call = std::pin::pin!(call); - let mut started = false; - std::future::poll_fn(|cx| { - let poll = call.as_mut().poll(cx); - if poll.is_pending() && !started { - started = true; - trace_guest(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); - } - poll - }) - .await - } - }, - _ => quote! {}, - }; let panic_hook = panic_hook(&self.mode); parse_quote! { #sig { #panic_hook - #start_defs let #executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); #body join_spawned().await; @@ -519,6 +492,21 @@ impl State { run_executor().await; EXECUTOR.with_borrow_mut(|e| e.stopped = false); }, + // In record mode, the exports component runs it for each call of an async export (see + // `call_export_recorded`): a call while it runs, or still finishes its jobs after a + // `stop`, keeps it running instead. + ("run", Mode::Record) => quote! { + thread_local! { + static RUNNING: std::cell::Cell = const { std::cell::Cell::new(false) }; + } + if RUNNING.replace(true) { + EXECUTOR.with_borrow_mut(|e| e.stopped = false); + return; + } + run_executor().await; + RUNNING.set(false); + EXECUTOR.with_borrow_mut(|e| e.stopped = false); + }, ("run", _) => { let panic_hook = panic_hook(&self.mode); quote! { @@ -643,6 +631,91 @@ fn job_defs(mode: &Mode, has_executor: Option) -> Vec { items } +/// Helpers of the exports component's calls of the main component's exports, which record, +/// fuzz and replay modes share, so that they stay alike, as replay requires: record and fuzz +/// modes record a guest line, which replay checks. +fn export_call_defs(mode: &Mode) -> Vec { + let trace_guest = match mode { + Mode::Record | Mode::Fuzz => quote! { proxy::recorder::record::record(line); }, + Mode::Replay => quote! { proxy::recorder::replay::check(line); }, + Mode::Dialog => return Vec::new(), + }; + let file: File = parse_quote! { + /// A line of the guest, which record and fuzz modes record, and replay checks. + #[allow(dead_code)] + fn trace_guest(line: &Line) { + #trace_guest + } + /// Calls an async export, and reports that the guest started it, if it blocks. + #[allow(dead_code)] + async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + trace_guest(&Line { by: Party::Guest, at: None, event: Event::Started(id) }); + } + poll + }) + .await + } + }; + file.items +} + +/// Helpers of record mode, which forwards the main component's calls to the host, and the host's +/// to it, for each component. +fn record_defs(has_executor: Option) -> Vec { + let file: File = match has_executor { + // The imports component, whose executor awaits the host's async calls. + Some(HasExecutor::Exports) => parse_quote! { + /// The host's result of an async call: at once, if it is ready on the first poll, so + /// that the guest sees RETURNED, and otherwise from an executor job, which awaits the + /// host's subtask, with the step of the run it completed in, as replay returns it in + /// that step. The call may borrow the guest's arguments, e.g. a method's `self`. + #[allow(dead_code)] + async fn host_result( + id: TaskId, + call: impl std::future::Future, + ) -> (T, Option) { + let mut call = Box::pin(call); + let first = std::future::poll_fn(|cx| std::task::Poll::Ready(call.as_mut().poll(cx))).await; + if let std::task::Poll::Ready(value) = first { + return (value, None); + } + proxy::recorder::record::record(&Line { by: Party::Host, at: None, event: Event::Started(id) }); + let (value, at) = wait_for_job(call).await; + (value, Some(at)) + } + }, + // The exports component, which runs the imports component's executor while the host's + // calls of async exports are pending: there is no `start` to run it. + Some(HasExecutor::Imports) => parse_quote! { + thread_local! { + // The host's calls of async exports that haven't returned. + static PENDING_CALLS: std::cell::Cell = const { std::cell::Cell::new(0) }; + } + /// Calls an async export, as `call_export` does, running the executor until the last + /// pending call returns, then stopping it, which lets it finish its jobs and return. + #[allow(dead_code)] + async fn call_export_recorded(id: TaskId, call: impl std::future::Future) -> T { + PENDING_CALLS.set(PENDING_CALLS.get() + 1); + spawn(proxy::conversion::executor::run()); + let res = call_export(id, call).await; + PENDING_CALLS.set(PENDING_CALLS.get() - 1); + if PENDING_CALLS.get() == 0 { + proxy::conversion::executor::stop(); + } + res + } + }, + None => return Vec::new(), + }; + file.items +} + /// Sends the panics of the component to the recorder, which reports them (see `fail`), in /// fuzz and replay modes: the generated components have no stderr of their own to print them, /// e.g. when the main component's is virtualized. @@ -723,6 +796,7 @@ fn start_spawn_defs() -> Vec { SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); } // Awaits the spawned jobs, including those they spawn. + #[allow(dead_code)] async fn join_spawned() { loop { let spawned = SPAWNED.with_borrow_mut(std::mem::take); diff --git a/src/codegen/record.rs b/src/codegen/record.rs index 4f7b3f1..9989edc 100644 --- a/src/codegen/record.rs +++ b/src/codegen/record.rs @@ -56,21 +56,41 @@ impl State { let is_async = wit_func.is_async; // The host calls the exports, which the exports component numbers, and the guest the // imports, which the imports component numbers. - let (table, caller, callee) = if is_wrapped_module(&module_path[1]) { + let is_import = is_wrapped_module(&module_path[1]); + let (table, caller, callee) = if is_import { (quote! { Imports }, quote! { Guest }, quote! { Host }) } else { (quote! { Exports }, quote! { Host }, quote! { Guest }) }; + let call = quote! { #func(#(#call_args),*) }; + let (record_call, call) = match (is_import, is_async) { + (true, false) => (quote! { None }, quote! { (#call, None) }), + // The host's result, at once (RETURNED), or in the step of the executor's run in which + // it completed (see `host_result`). + (true, true) => ( + quote! { None }, + quote! { host_result(__id, async move { #call.await }).await }, + ), + // An export call's arrival is a step, as in fuzz and replay modes. + (false, false) => ( + quote! { Some(proxy::conversion::executor::step()) }, + quote! { (#call, None) }, + ), + (false, true) => ( + quote! { Some(proxy::conversion::executor::step()) }, + quote! { (call_export_recorded(__id, #call).await, None) }, + ), + }; let record_ret = if get_return_type(&sig.output).is_none() { quote! { - #func(#(#call_args),*); - proxy::recorder::record::record(&trace_return(Party::#callee, __id, None)); + let ((), __at) = #call; + proxy::recorder::record::record(&Line { at: __at, ..trace_return(Party::#callee, __id, None) }); } } else { quote! { - let res = #func(#(#call_args),*); + let (res, __at) = #call; let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); - proxy::recorder::record::record(&trace_return(Party::#callee, __id, Some(wave_res))); + proxy::recorder::record::record(&Line { at: __at, ..trace_return(Party::#callee, __id, Some(wave_res)) }); #res } }; @@ -82,7 +102,11 @@ impl State { let #arg_names = #arg_names.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* - proxy::recorder::record::record(&trace_call(Party::#caller, __id, #display_name, #is_async, params)); + let __at: Option = #record_call; + proxy::recorder::record::record(&Line { + at: __at, + ..trace_call(Party::#caller, __id, #display_name, #is_async, params) + }); #record_ret } } diff --git a/tests/snapshots/dialog/record_exports/lib.rs b/tests/snapshots/dialog/record_exports/lib.rs index 8dcad20..59e6a72 100644 --- a/tests/snapshots/dialog/record_exports/lib.rs +++ b/tests/snapshots/dialog/record_exports/lib.rs @@ -642,6 +642,7 @@ fn spawn(job: impl std::future::Future + 'static) { let handle = wit_bindgen::spawn_local(job); SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); } +#[allow(dead_code)] async fn join_spawned() { loop { let spawned = SPAWNED.with_borrow_mut(std::mem::take); diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index ad93e19..362ee45 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -680,23 +680,45 @@ async fn reach_step(step: u32) { } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. /// /// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so /// that what the task does next, e.g. return a STARTED call, or take an export call's step, /// follows the run it waited for, in record and replay alike: replay knows only the step. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { - #[derive(Default)] - struct Done { - step: Option, +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>( + job: impl std::future::Future + 'a, +) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, waker: Option, held: bool, } - /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. - /// the mock of an import the guest cancelled. - struct Waiter(std::rc::Rc>); - impl Drop for Waiter { + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { fn drop(&mut self) { - if std::mem::take(&mut self.0.borrow_mut().held) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { HELD.set(HELD.get() - 1); if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { wake_executor(); @@ -704,28 +726,39 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { } } } - let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job: Job<'a, T> = Box::pin(job); + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { + job: Some(job), + value: None, + waker: None, + held: false, + }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); let job_shared = waiter.0.clone(); spawn( - std::future::poll_fn(move |_| { - if done() { - HELD.set(HELD.get() + 1); - let mut shared = job_shared.borrow_mut(); - shared.step = Some(current_step()); - shared.held = true; - if let Some(waker) = shared.waker.take() { - waker.wake(); - } + std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); } - WAKE_AGAIN.set(true); - std::task::Poll::Pending + std::task::Poll::Ready(()) }), ); - let step = std::future::poll_fn(|cx| { + let value = std::future::poll_fn(|cx| { let mut shared = waiter.0.borrow_mut(); - match shared.step { - Some(step) => std::task::Poll::Ready(step), + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), None => { shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending @@ -734,7 +767,7 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { }) .await; drop(waiter); - step + value } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index 42041ad..8c25ae2 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -9,35 +9,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .call_once(|| std::panic::set_hook( Box::new(|info| proxy::recorder::record::fail(&info.to_string())), )); - /// A line of the guest, which fuzz mode records, and replay checks. - #[allow(dead_code)] - fn trace_guest(line: &Line) { - proxy::recorder::record::record(line); - } - /// Calls an async export, and reports that the guest started it, if it blocks. - #[allow(dead_code)] - async fn call_export( - id: TaskId, - call: impl std::future::Future, - ) -> T { - let mut call = std::pin::pin!(call); - let mut started = false; - std::future::poll_fn(|cx| { - let poll = call.as_mut().poll(cx); - if poll.is_pending() && !started { - started = true; - trace_guest( - &Line { - by: Party::Guest, - at: None, - event: Event::Started(id), - }, - ); - } - poll - }) - .await - } let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); @@ -731,6 +702,32 @@ fn host_dropping(f: impl FnOnce() -> R) -> R { proxy::conversion::executor::host_drops(false); res } +/// A line of the guest, which record and fuzz modes record, and replay checks. +#[allow(dead_code)] +fn trace_guest(line: &Line) { + proxy::recorder::record::record(line); +} +/// Calls an async export, and reports that the guest started it, if it blocks. +#[allow(dead_code)] +async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + trace_guest( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; @@ -753,6 +750,7 @@ fn spawn(job: impl std::future::Future + 'static) { let handle = wit_bindgen::spawn_local(job); SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); } +#[allow(dead_code)] async fn join_spawned() { loop { let spawned = SPAWNED.with_borrow_mut(std::mem::take); diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 469d7e8..6707bcb 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -906,23 +906,45 @@ async fn reach_step(step: u32) { } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. /// /// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so /// that what the task does next, e.g. return a STARTED call, or take an export call's step, /// follows the run it waited for, in record and replay alike: replay knows only the step. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { - #[derive(Default)] - struct Done { - step: Option, +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>( + job: impl std::future::Future + 'a, +) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, waker: Option, held: bool, } - /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. - /// the mock of an import the guest cancelled. - struct Waiter(std::rc::Rc>); - impl Drop for Waiter { + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { fn drop(&mut self) { - if std::mem::take(&mut self.0.borrow_mut().held) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { HELD.set(HELD.get() - 1); if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { wake_executor(); @@ -930,28 +952,39 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { } } } - let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job: Job<'a, T> = Box::pin(job); + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { + job: Some(job), + value: None, + waker: None, + held: false, + }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); let job_shared = waiter.0.clone(); spawn( - std::future::poll_fn(move |_| { - if done() { - HELD.set(HELD.get() + 1); - let mut shared = job_shared.borrow_mut(); - shared.step = Some(current_step()); - shared.held = true; - if let Some(waker) = shared.waker.take() { - waker.wake(); - } + std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); } - WAKE_AGAIN.set(true); - std::task::Poll::Pending + std::task::Poll::Ready(()) }), ); - let step = std::future::poll_fn(|cx| { + let value = std::future::poll_fn(|cx| { let mut shared = waiter.0.borrow_mut(); - match shared.step { - Some(step) => std::task::Poll::Ready(step), + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), None => { shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending @@ -960,7 +993,7 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { }) .await; drop(waiter); - step + value } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index c00e22e..0bbb4b7 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -543,6 +543,7 @@ fn spawn(job: impl std::future::Future + 'static) { let handle = wit_bindgen::spawn_local(job); SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); } +#[allow(dead_code)] async fn join_spawned() { loop { let spawned = SPAWNED.with_borrow_mut(std::mem::take); diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index e75aacd..ba7da3a 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -650,23 +650,45 @@ async fn reach_step(step: u32) { } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. /// /// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so /// that what the task does next, e.g. return a STARTED call, or take an export call's step, /// follows the run it waited for, in record and replay alike: replay knows only the step. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { - #[derive(Default)] - struct Done { - step: Option, +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>( + job: impl std::future::Future + 'a, +) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, waker: Option, held: bool, } - /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. - /// the mock of an import the guest cancelled. - struct Waiter(std::rc::Rc>); - impl Drop for Waiter { + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { fn drop(&mut self) { - if std::mem::take(&mut self.0.borrow_mut().held) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { HELD.set(HELD.get() - 1); if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { wake_executor(); @@ -674,28 +696,39 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { } } } - let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job: Job<'a, T> = Box::pin(job); + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { + job: Some(job), + value: None, + waker: None, + held: false, + }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); let job_shared = waiter.0.clone(); spawn( - std::future::poll_fn(move |_| { - if done() { - HELD.set(HELD.get() + 1); - let mut shared = job_shared.borrow_mut(); - shared.step = Some(current_step()); - shared.held = true; - if let Some(waker) = shared.waker.take() { - waker.wake(); - } + std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); } - WAKE_AGAIN.set(true); - std::task::Poll::Pending + std::task::Poll::Ready(()) }), ); - let step = std::future::poll_fn(|cx| { + let value = std::future::poll_fn(|cx| { let mut shared = waiter.0.borrow_mut(); - match shared.step { - Some(step) => std::task::Poll::Ready(step), + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), None => { shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending @@ -704,7 +737,7 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { }) .await; drop(waiter); - step + value } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 061f8e7..e5d2fcd 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -9,35 +9,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .call_once(|| std::panic::set_hook( Box::new(|info| proxy::recorder::record::fail(&info.to_string())), )); - /// A line of the guest, which fuzz mode records, and replay checks. - #[allow(dead_code)] - fn trace_guest(line: &Line) { - proxy::recorder::record::record(line); - } - /// Calls an async export, and reports that the guest started it, if it blocks. - #[allow(dead_code)] - async fn call_export( - id: TaskId, - call: impl std::future::Future, - ) -> T { - let mut call = std::pin::pin!(call); - let mut started = false; - std::future::poll_fn(|cx| { - let poll = call.as_mut().poll(cx); - if poll.is_pending() && !started { - started = true; - trace_guest( - &Line { - by: Party::Guest, - at: None, - event: Event::Started(id), - }, - ); - } - poll - }) - .await - } let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); let __buf = proxy::util::debug::get_random(); let mut u = Unstructured::new(&__buf); @@ -715,6 +686,32 @@ fn host_dropping(f: impl FnOnce() -> R) -> R { proxy::conversion::executor::host_drops(false); res } +/// A line of the guest, which record and fuzz modes record, and replay checks. +#[allow(dead_code)] +fn trace_guest(line: &Line) { + proxy::recorder::record::record(line); +} +/// Calls an async export, and reports that the guest started it, if it blocks. +#[allow(dead_code)] +async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + trace_guest( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; @@ -737,6 +734,7 @@ fn spawn(job: impl std::future::Future + 'static) { let handle = wit_bindgen::spawn_local(job); SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); } +#[allow(dead_code)] async fn join_spawned() { loop { let spawned = SPAWNED.with_borrow_mut(std::mem::take); diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 6c19002..6f22bde 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -959,23 +959,45 @@ async fn reach_step(step: u32) { } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. /// /// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so /// that what the task does next, e.g. return a STARTED call, or take an export call's step, /// follows the run it waited for, in record and replay alike: replay knows only the step. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { - #[derive(Default)] - struct Done { - step: Option, +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>( + job: impl std::future::Future + 'a, +) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, waker: Option, held: bool, } - /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. - /// the mock of an import the guest cancelled. - struct Waiter(std::rc::Rc>); - impl Drop for Waiter { + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { fn drop(&mut self) { - if std::mem::take(&mut self.0.borrow_mut().held) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { HELD.set(HELD.get() - 1); if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { wake_executor(); @@ -983,28 +1005,39 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { } } } - let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job: Job<'a, T> = Box::pin(job); + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { + job: Some(job), + value: None, + waker: None, + held: false, + }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); let job_shared = waiter.0.clone(); spawn( - std::future::poll_fn(move |_| { - if done() { - HELD.set(HELD.get() + 1); - let mut shared = job_shared.borrow_mut(); - shared.step = Some(current_step()); - shared.held = true; - if let Some(waker) = shared.waker.take() { - waker.wake(); - } + std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); } - WAKE_AGAIN.set(true); - std::task::Poll::Pending + std::task::Poll::Ready(()) }), ); - let step = std::future::poll_fn(|cx| { + let value = std::future::poll_fn(|cx| { let mut shared = waiter.0.borrow_mut(); - match shared.step { - Some(step) => std::task::Poll::Ready(step), + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), None => { shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending @@ -1013,7 +1046,7 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { }) .await; drop(waiter); - step + value } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] diff --git a/tests/snapshots/record/record_exports/lib.rs b/tests/snapshots/record/record_exports/lib.rs index 216f30f..7195a44 100644 --- a/tests/snapshots/record/record_exports/lib.rs +++ b/tests/snapshots/record/record_exports/lib.rs @@ -14,13 +14,26 @@ impl exports::test::guest::api::Guest for Stub { params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); + let __at: Option = Some(proxy::conversion::executor::step()); proxy::recorder::record::record( - &trace_call(Party::Host, __id, "test:guest/api@0.1.0.run", false, params), + &Line { + at: __at, + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.run", + false, + params, + ) + }, ); - let res = wrapped_test::guest::api::run(&p, &c); + let (res, __at) = (wrapped_test::guest::api::run(&p, &c), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Guest, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Guest, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -31,19 +44,26 @@ impl exports::test::guest::api::Guest for Stub { let mut params: Vec = Vec::new(); let s = s.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); + let __at: Option = Some(proxy::conversion::executor::step()); proxy::recorder::record::record( - &trace_call( - Party::Host, - __id, - "test:guest/api@0.1.0.classify", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.classify", + false, + params, + ) + }, ); - let res = wrapped_test::guest::api::classify(&s); + let (res, __at) = (wrapped_test::guest::api::classify(&s), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Guest, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Guest, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -54,13 +74,26 @@ impl exports::test::guest::api::Guest for Stub { let mut params: Vec = Vec::new(); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); + let __at: Option = Some(proxy::conversion::executor::step()); proxy::recorder::record::record( - &trace_call(Party::Host, __id, "test:guest/api@0.1.0.begin", false, params), + &Line { + at: __at, + ..trace_call( + Party::Host, + __id, + "test:guest/api@0.1.0.begin", + false, + params, + ) + }, ); - let res = wrapped_test::guest::api::begin(c); + let (res, __at) = (wrapped_test::guest::api::begin(c), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Guest, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Guest, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -71,19 +104,26 @@ impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Sessi let mut params: Vec = Vec::new(); let name = name.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); + let __at: Option = Some(proxy::conversion::executor::step()); proxy::recorder::record::record( - &trace_call( - Party::Host, - __id, - "new test:guest/api/session@0.1.0", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Host, + __id, + "new test:guest/api/session@0.1.0", + false, + params, + ) + }, ); - let res = Self::new(&name); + let (res, __at) = (Self::new(&name), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Guest, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Guest, __id, Some(wave_res)) + }, ); res } @@ -92,19 +132,26 @@ impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Sessi let params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; + let __at: Option = Some(proxy::conversion::executor::step()); proxy::recorder::record::record( - &trace_call( - Party::Host, - __id, - "test:guest/api/session@0.1.0.name", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Host, + __id, + "test:guest/api/session@0.1.0.name", + false, + params, + ) + }, ); - let res = self.name(); + let (res, __at) = (self.name(), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Guest, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Guest, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -1323,3 +1370,69 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// A line of the guest, which record and fuzz modes record, and replay checks. +#[allow(dead_code)] +fn trace_guest(line: &Line) { + proxy::recorder::record::record(line); +} +/// Calls an async export, and reports that the guest started it, if it blocks. +#[allow(dead_code)] +async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + trace_guest( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await +} +thread_local! { + static PENDING_CALLS : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; +} +/// Calls an async export, as `call_export` does, running the executor until the last +/// pending call returns, then stopping it, which lets it finish its jobs and return. +#[allow(dead_code)] +async fn call_export_recorded( + id: TaskId, + call: impl std::future::Future, +) -> T { + PENDING_CALLS.set(PENDING_CALLS.get() + 1); + spawn(proxy::conversion::executor::run()); + let res = call_export(id, call).await; + PENDING_CALLS.set(PENDING_CALLS.get() - 1); + if PENDING_CALLS.get() == 0 { + proxy::conversion::executor::stop(); + } + res +} +thread_local! { + static SPAWNED : std::cell::RefCell < Vec < wit_bindgen::JoinHandle < () >> > = const + { std::cell::RefCell::new(Vec::new()) }; +} +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + let handle = wit_bindgen::spawn_local(job); + SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); +} +#[allow(dead_code)] +async fn join_spawned() { + loop { + let spawned = SPAWNED.with_borrow_mut(std::mem::take); + if spawned.is_empty() { + break; + } + for handle in spawned { + handle.await; + } + } +} diff --git a/tests/snapshots/record/record_imports/lib.rs b/tests/snapshots/record/record_imports/lib.rs index 3af812e..38540ba 100644 --- a/tests/snapshots/record/record_imports/lib.rs +++ b/tests/snapshots/record/record_imports/lib.rs @@ -19,6 +19,39 @@ impl exports::proxy::conversion::conversion::Guest for Stub { x.to_proxy().handle() } } +impl exports::proxy::conversion::executor::Guest for Stub { + async fn run() -> () { + thread_local! { + static RUNNING : std::cell::Cell < bool > = const { + std::cell::Cell::new(false) }; + } + if RUNNING.replace(true) { + EXECUTOR.with_borrow_mut(|e| e.stopped = false); + return; + } + run_executor().await; + RUNNING.set(false); + EXECUTOR.with_borrow_mut(|e| e.stopped = false); + } + fn stop() -> () { + stop_executor() + } + fn step() -> u32 { + next_step() + } + fn current_step() -> u32 { + current_step() + } + async fn wait_until_step(step: u32) -> () { + wait_until_step(step).await + } + async fn step_after(step: u32) -> u32 { + step_after(step).await + } + fn host_drops(dropping: bool) -> () { + let _ = dropping; + } +} impl exports::wrapped_test::host::types::Guest for Stub { type Counter = test::host::types::Counter; fn make_point(x: i32, y: i32) -> exports::wrapped_test::host::types::Point { @@ -28,19 +61,26 @@ impl exports::wrapped_test::host::types::Guest for Stub { params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); let y = y.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&y)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call( - Party::Guest, - __id, - "test:host/types@0.1.0.make-point", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.make-point", + false, + params, + ) + }, ); - let res = test::host::types::make_point(x, y); + let (res, __at) = (test::host::types::make_point(x, y), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -54,13 +94,26 @@ impl exports::wrapped_test::host::types::Guest for Stub { params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call(Party::Guest, __id, "test:host/types@0.1.0.check", false, params), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.check", + false, + params, + ) + }, ); - let res = test::host::types::check(p, c); + let (res, __at) = (test::host::types::check(p, c), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -71,13 +124,26 @@ impl exports::wrapped_test::host::types::Guest for Stub { let mut params: Vec = Vec::new(); let name = name.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call(Party::Guest, __id, "test:host/types@0.1.0.open", false, params), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:host/types@0.1.0.open", + false, + params, + ) + }, ); - let res = test::host::types::open(&name); + let (res, __at) = (test::host::types::open(&name), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -88,19 +154,26 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou let mut params: Vec = Vec::new(); let start = start.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call( - Party::Guest, - __id, - "new test:host/types/counter@0.1.0", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "new test:host/types/counter@0.1.0", + false, + params, + ) + }, ); - let res = Self::new(start); + let (res, __at) = (Self::new(start), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res } @@ -109,19 +182,26 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou let params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; + let __at: Option = None; proxy::recorder::record::record( - &trace_call( - Party::Guest, - __id, - "test:host/types/counter@0.1.0.get", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.get", + false, + params, + ) + }, ); - let res = self.get(); + let (res, __at) = (self.get(), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -132,17 +212,26 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou ]; let n = n.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&n)).unwrap()); + let __at: Option = None; + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.add", + false, + params, + ) + }, + ); + let ((), __at) = (self.add(n), None); proxy::recorder::record::record( - &trace_call( - Party::Guest, - __id, - "test:host/types/counter@0.1.0.add", - false, - params, - ), + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, ); - self.add(n); - proxy::recorder::record::record(&trace_return(Party::Host, __id, None)); } fn merge( a: exports::wrapped_test::host::types::CounterBorrow<'_>, @@ -154,19 +243,26 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); let b = b.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&b)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call( - Party::Guest, - __id, - "test:host/types/counter@0.1.0.merge", - false, - params, - ), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:host/types/counter@0.1.0.merge", + false, + params, + ) + }, ); - let res = Self::merge(&a, b); + let (res, __at) = (Self::merge(&a, b), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -187,11 +283,26 @@ impl exports::wrapped_test::log0_1_0::sink::Guest for Stub { let mut params: Vec = Vec::new(); let msg = msg.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call(Party::Guest, __id, "test:log/sink@0.1.0.write", false, params), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:log/sink@0.1.0.write", + false, + params, + ) + }, + ); + let ((), __at) = (test::log0_1_0::sink::write(&msg), None); + proxy::recorder::record::record( + &Line { + at: __at, + ..trace_return(Party::Host, __id, None) + }, ); - test::log0_1_0::sink::write(&msg); - proxy::recorder::record::record(&trace_return(Party::Host, __id, None)); } } impl exports::wrapped_test::log0_2_0::sink::Guest for Stub { @@ -200,13 +311,26 @@ impl exports::wrapped_test::log0_2_0::sink::Guest for Stub { let mut params: Vec = Vec::new(); let msg = msg.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); + let __at: Option = None; proxy::recorder::record::record( - &trace_call(Party::Guest, __id, "test:log/sink@0.2.0.write", false, params), + &Line { + at: __at, + ..trace_call( + Party::Guest, + __id, + "test:log/sink@0.2.0.write", + false, + params, + ) + }, ); - let res = test::log0_2_0::sink::write(&msg); + let (res, __at) = (test::log0_2_0::sink::write(&msg), None); let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record( - &trace_return(Party::Host, __id, Some(wave_res)), + &Line { + at: __at, + ..trace_return(Party::Host, __id, Some(wave_res)) + }, ); res.to_proxy() } @@ -1341,3 +1465,255 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The host's result of an async call: at once, if it is ready on the first poll, so +/// that the guest sees RETURNED, and otherwise from an executor job, which awaits the +/// host's subtask, with the step of the run it completed in, as replay returns it in +/// that step. The call may borrow the guest's arguments, e.g. a method's `self`. +#[allow(dead_code)] +async fn host_result( + id: TaskId, + call: impl std::future::Future, +) -> (T, Option) { + let mut call = Box::pin(call); + let first = std::future::poll_fn(|cx| std::task::Poll::Ready(call.as_mut().poll(cx))) + .await; + if let std::task::Poll::Ready(value) = first { + return (value, None); + } + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Started(id), + }, + ); + let (value, at) = wait_for_job(call).await; + (value, Some(at)) +} +type Job = std::pin::Pin>>; +#[derive(Default)] +struct Executor { + queue: Vec, + stopped: bool, + waker: Option, +} +thread_local! { + static EXECUTOR : std::cell::RefCell < Executor > = + std::cell::RefCell::new(Executor::default()); static STEP : std::cell::Cell < u32 > = + const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const + { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; +} +/// Counts a step, and returns it. +#[allow(dead_code)] +fn next_step() -> u32 { + STEP.set(STEP.get() + 1); + STEP.get() +} +/// The step a job runs in. +#[allow(dead_code)] +fn current_step() -> u32 { + STEP.get() +} +/// Waits, from any task, until the executor has run `runs` more times, and returns the step of +/// the last run. +#[allow(dead_code)] +async fn wait_for_runs(runs: u32) -> u32 { + let mut left = runs; + wait_for_run(move || { + if left == 0 { + return true; + } + left -= 1; + false + }) + .await +} +/// Waits, from any task, until a run of the executor in `step` or later, or returns at once if +/// the count is already there, e.g. replay's for a host line recorded in that step, which checks +/// that it came in that step. +#[allow(dead_code)] +async fn wait_until_step(step: u32) { + if current_step() >= step { + return; + } + wait_for_run(move || current_step() >= step).await; +} +/// Waits, from any task, as `wait_until_step` does, and takes the next step, of a host line +/// `start` makes, e.g. an export call's arrival, which it returns: the one after the run it +/// waited for, as the executor doesn't run in between (see `wait_for_run`). +#[allow(dead_code)] +async fn step_after(step: u32) -> u32 { + wait_until_step(step).await; + next_step() +} +/// Waits, in a job of the executor, until its run in `step`, waking the executor itself until +/// then, e.g. replay's job for a copy recorded in that step. +/// +/// Panics if the count passed `step`: replay diverged from the recording. +#[allow(dead_code)] +async fn reach_step(step: u32) { + std::future::poll_fn(|_| { + let now = current_step(); + assert!( + now <= step, "replay reached step {now} before the run in step {step}" + ); + if now == step { + std::task::Poll::Ready(()) + } else { + WAKE_AGAIN.set(true); + std::task::Poll::Pending + } + }) + .await +} +/// Waits until a run of the executor in which `done` returns true, and returns its step. A job +/// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. +/// +/// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so +/// that what the task does next, e.g. return a STARTED call, or take an export call's step, +/// follows the run it waited for, in record and replay alike: replay knows only the step. +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>( + job: impl std::future::Future + 'a, +) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, + waker: Option, + held: bool, + } + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { + fn drop(&mut self) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { + HELD.set(HELD.get() - 1); + if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { + wake_executor(); + } + } + } + } + let job: Job<'a, T> = Box::pin(job); + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { + job: Some(job), + value: None, + waker: None, + held: false, + }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); + let job_shared = waiter.0.clone(); + spawn( + std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { + return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); + } + std::task::Poll::Ready(()) + }), + ); + let value = std::future::poll_fn(|cx| { + let mut shared = waiter.0.borrow_mut(); + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), + None => { + shared.waker = Some(cx.waker().clone()); + std::task::Poll::Pending + } + } + }) + .await; + drop(waiter); + value +} +/// Queues `job`, from any function, sync or async. +#[allow(dead_code)] +fn spawn(job: impl std::future::Future + 'static) { + EXECUTOR + .with_borrow_mut(|e| { + e.queue.push(Box::pin(job)); + }); + wake_executor(); +} +/// Wakes `run_executor` from another task, with wit-bindgen's `inter-task-wakeup`. +fn wake_executor() { + let waker = EXECUTOR.with_borrow_mut(|e| e.waker.take()); + if let Some(waker) = waker { + waker.wake(); + } +} +/// Polls the queued jobs until `stop_executor` is called and they are all done. +#[allow(dead_code)] +async fn run_executor() { + let mut running: Vec = Vec::new(); + std::future::poll_fn(|cx| { + if HELD.get() > 0 { + WOKEN_WHILE_HELD.set(true); + EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); + return std::task::Poll::Pending; + } + next_step(); + WAKE_AGAIN.set(false); + running.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + loop { + let mut queued = EXECUTOR + .with_borrow_mut(|e| std::mem::take(&mut e.queue)); + if queued.is_empty() { + break; + } + queued.retain_mut(|job| job.as_mut().poll(cx).is_pending()); + running.append(&mut queued); + } + EXECUTOR + .with_borrow_mut(|e| { + if e.stopped && running.is_empty() { + std::task::Poll::Ready(()) + } else { + e.waker = Some(cx.waker().clone()); + if WAKE_AGAIN.get() { + cx.waker().wake_by_ref(); + } + std::task::Poll::Pending + } + }) + }) + .await +} +#[allow(dead_code)] +fn stop_executor() { + EXECUTOR.with_borrow_mut(|e| e.stopped = true); + wake_executor(); +} diff --git a/tests/snapshots/record/wit/component.wit b/tests/snapshots/record/wit/component.wit index 020c58a..95349c5 100644 --- a/tests/snapshots/record/wit/component.wit +++ b/tests/snapshots/record/wit/component.wit @@ -2,6 +2,7 @@ package component:proxy; world imports { import proxy:recorder/%record@0.1.0; export proxy:conversion/conversion; + export proxy:conversion/executor; import test:host/types@0.1.0; export wrapped-test:host/types@0.1.0; import test:log/sink@0.1.0; @@ -16,6 +17,7 @@ world tmp-exports { world exports { import proxy:recorder/%record@0.1.0; import proxy:conversion/conversion; + import proxy:conversion/executor; import wrapped-test:host/types@0.1.0; import wrapped-test:guest/api@0.1.0; import test:host/types@0.1.0; diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index 8d607d6..434d954 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -9,35 +9,6 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { .call_once(|| std::panic::set_hook( Box::new(|info| proxy::recorder::replay::fail(&info.to_string())), )); - /// A line of the guest, which fuzz mode records, and replay checks. - #[allow(dead_code)] - fn trace_guest(line: &Line) { - proxy::recorder::replay::check(line); - } - /// Calls an async export, and reports that the guest started it, if it blocks. - #[allow(dead_code)] - async fn call_export( - id: TaskId, - call: impl std::future::Future, - ) -> T { - let mut call = std::pin::pin!(call); - let mut started = false; - std::future::poll_fn(|cx| { - let poll = call.as_mut().poll(cx); - if poll.is_pending() && !started { - started = true; - trace_guest( - &Line { - by: Party::Guest, - at: None, - event: Event::Started(id), - }, - ); - } - poll - }) - .await - } let __executor = wit_bindgen::spawn_local(proxy::conversion::executor::run()); while let Some(line) = proxy::recorder::replay::next_host_line() { if let Some(at) = line.at { @@ -695,6 +666,32 @@ fn host_dropping(f: impl FnOnce() -> R) -> R { proxy::conversion::executor::host_drops(false); res } +/// A line of the guest, which record and fuzz modes record, and replay checks. +#[allow(dead_code)] +fn trace_guest(line: &Line) { + proxy::recorder::replay::check(line); +} +/// Calls an async export, and reports that the guest started it, if it blocks. +#[allow(dead_code)] +async fn call_export(id: TaskId, call: impl std::future::Future) -> T { + let mut call = std::pin::pin!(call); + let mut started = false; + std::future::poll_fn(|cx| { + let poll = call.as_mut().poll(cx); + if poll.is_pending() && !started { + started = true; + trace_guest( + &Line { + by: Party::Guest, + at: None, + event: Event::Started(id), + }, + ); + } + poll + }) + .await +} thread_local! { #[allow(dead_code)] static MOVING : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; @@ -708,6 +705,7 @@ fn spawn(job: impl std::future::Future + 'static) { let handle = wit_bindgen::spawn_local(job); SPAWNED.with_borrow_mut(|spawned| spawned.push(handle)); } +#[allow(dead_code)] async fn join_spawned() { loop { let spawned = SPAWNED.with_borrow_mut(std::mem::take); diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index 68fab7a..d3d0f91 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -833,23 +833,45 @@ async fn reach_step(step: u32) { } /// Waits until a run of the executor in which `done` returns true, and returns its step. A job /// asks in each run, waking the executor itself in between, as no host event would. +async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { + let job = std::future::poll_fn(move |_| { + if done() { + return std::task::Poll::Ready(()); + } + WAKE_AGAIN.set(true); + std::task::Poll::Pending + }); + wait_for_job(job).await.1 +} +/// Runs `job` in the executor, from any task, and returns its value, and the step of the run it +/// completed in, e.g. record mode's host call that blocked, whose completion wakes the executor. /// /// The executor doesn't run again until the waiting task resumes, e.g. for a guest's event, so /// that what the task does next, e.g. return a STARTED call, or take an export call's step, /// follows the run it waited for, in record and replay alike: replay knows only the step. -async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { - #[derive(Default)] - struct Done { - step: Option, +/// +/// `job` may borrow from the waiting task, e.g. a host call on the resource the guest passed: it +/// is only polled while the waiting task waits, and dropped if the task is dropped first, e.g. +/// the guest cancelled its call, which cancels the host's call too. +#[allow(dead_code)] +async fn wait_for_job<'a, T: 'static>( + job: impl std::future::Future + 'a, +) -> (T, u32) { + type Job<'a, T> = std::pin::Pin + 'a>>; + struct Done { + job: Option>, + value: Option<(T, u32)>, waker: Option, held: bool, } - /// The waiter's side, which releases the executor once it resumes, or is dropped first, e.g. - /// the mock of an import the guest cancelled. - struct Waiter(std::rc::Rc>); - impl Drop for Waiter { + /// The waiting task's side, which drops the job if it is still pending, and releases the + /// executor once the task resumes, or is dropped first. + struct Waiter(std::rc::Rc>>); + impl Drop for Waiter { fn drop(&mut self) { - if std::mem::take(&mut self.0.borrow_mut().held) { + let mut done = self.0.borrow_mut(); + drop(done.job.take()); + if std::mem::take(&mut done.held) { HELD.set(HELD.get() - 1); if HELD.get() == 0 && WOKEN_WHILE_HELD.replace(false) { wake_executor(); @@ -857,28 +879,39 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { } } } - let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(Done::default()))); + let job: Job<'a, T> = Box::pin(job); + let job: Job<'static, T> = unsafe { std::mem::transmute(job) }; + let done = Done { + job: Some(job), + value: None, + waker: None, + held: false, + }; + let waiter = Waiter(std::rc::Rc::new(std::cell::RefCell::new(done))); let job_shared = waiter.0.clone(); spawn( - std::future::poll_fn(move |_| { - if done() { - HELD.set(HELD.get() + 1); - let mut shared = job_shared.borrow_mut(); - shared.step = Some(current_step()); - shared.held = true; - if let Some(waker) = shared.waker.take() { - waker.wake(); - } + std::future::poll_fn(move |cx| { + let mut shared = job_shared.borrow_mut(); + let Some(job) = shared.job.as_mut() else { return std::task::Poll::Ready(()); + }; + let std::task::Poll::Ready(value) = job.as_mut().poll(cx) else { + return std::task::Poll::Pending; + }; + shared.job = None; + HELD.set(HELD.get() + 1); + shared.value = Some((value, current_step())); + shared.held = true; + if let Some(waker) = shared.waker.take() { + waker.wake(); } - WAKE_AGAIN.set(true); - std::task::Poll::Pending + std::task::Poll::Ready(()) }), ); - let step = std::future::poll_fn(|cx| { + let value = std::future::poll_fn(|cx| { let mut shared = waiter.0.borrow_mut(); - match shared.step { - Some(step) => std::task::Poll::Ready(step), + match shared.value.take() { + Some(value) => std::task::Poll::Ready(value), None => { shared.waker = Some(cx.waker().clone()); std::task::Poll::Pending @@ -887,7 +920,7 @@ async fn wait_for_run(mut done: impl FnMut() -> bool + 'static) -> u32 { }) .await; drop(waiter); - step + value } /// Queues `job`, from any function, sync or async. #[allow(dead_code)] From b409ccff7b21d83a985afa878a82e743245dc092 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 16:32:35 -0800 Subject: [PATCH 21/25] Record streams and futures of p3 components, and consolidate fixtures - Record mode's pumps: converting a stream or future the proxy receives makes a pair of the other side's type, hands on its reader, and spawns a job between the received end and the new writer. The job records each copy (Write of the host's items, Read of the guest's), each side's Drop, and a future's Value, with their steps. A zero-length write makes it read from the sender only once the receiver reads, without read-ahead; it reads up to 4096 items at a time. SENDER says who sent the value: the caller for arguments, the callee for the result. - Labels: a pumped stream or future is labeled by the end the proxy received. Arguments are printed after conversion, so the reader handed on there is registered under that label with set_handle_label, and the items the caller sends are printed converted. - Executor: the executor's `run` callback wraps wit-bindgen's (executor_callback). instrument patches the generated bindings so that the executor's __callback_run calls it, and asserts the glue's shape. After a yield, the first poll is a probe that only wakes the executor, so wit-bindgen takes an event that came meanwhile before the real run, as when an event wakes a waiting executor during recording. Without it, a guest's write during a self-woken yield landed one step late in replay, breaking recordings whose streams carry several chunks. - tests/fixtures/p3-record: a guest whose async imports, streams and futures wasmtime implements itself. `run` makes two concurrent wait-for calls; `cat` copies stdin to stderr through WASI p3 streams. make test-record-p3 records both under `wasmtime run`, with delays for RETURNED and STARTED, and stdin in one chunk, several, or none, and replays each trace. - Fixtures: each WIT now sits next to the crates built from it: tests/fixtures/p2 (was proxy and proxy-run), tests/fixtures/p3 (was proxy-p3 and proxy-p3-run), and tests/fixtures/p3-record. - docs/p3-record-replay.md: pumps, their labels and readiness, the executor's callback, and phase 4's status. --- Makefile | 40 +- README.md | 2 +- assets/executor.rs | 35 +- docs/p3-record-replay.md | 39 +- src/codegen/mod.rs | 56 ++- src/codegen/record.rs | 2 + src/instrument.rs | 2 +- src/tests/codegen.rs | 7 +- src/traits/mod.rs | 2 + src/traits/proxy.rs | 174 ++++++++ tests/fixtures/{proxy-run => p2}/compose.wac | 0 .../{proxy-run => p2}/driver/Cargo.lock | 0 .../{proxy-run => p2}/driver/Cargo.toml | 0 .../{proxy-run => p2}/driver/src/lib.rs | 2 +- .../{proxy-run => p2}/driver/wit/world.wit | 0 .../{proxy-run => p2}/guest/Cargo.lock | 0 .../{proxy-run => p2}/guest/Cargo.toml | 0 .../{proxy-run => p2}/guest/src/lib.rs | 4 +- .../{proxy-run => p2}/host/Cargo.lock | 0 .../{proxy-run => p2}/host/Cargo.toml | 0 .../{proxy-run => p2}/host/src/lib.rs | 2 +- .../{proxy-run => p2}/host/wit/world.wit | 0 .../fixtures/{proxy => p2/wit}/component.wit | 0 .../fixtures/{proxy => p2/wit}/deps/guest.wit | 0 .../fixtures/{proxy => p2/wit}/deps/host.wit | 0 .../{proxy => p2/wit}/deps/log@0.1.0.wit | 0 .../{proxy => p2/wit}/deps/log@0.2.0.wit | 0 tests/fixtures/p3-record/guest/Cargo.lock | 410 ++++++++++++++++++ tests/fixtures/p3-record/guest/Cargo.toml | 14 + tests/fixtures/p3-record/guest/src/lib.rs | 87 ++++ .../{proxy-p3-run => p3}/guest/Cargo.lock | 0 .../{proxy-p3-run => p3}/guest/Cargo.toml | 0 .../{proxy-p3-run => p3}/guest/src/lib.rs | 4 +- .../{proxy-p3 => p3/wit}/component.wit | 2 +- .../fixtures/{proxy-p3 => p3/wit}/deps/p3.wit | 0 tests/snapshots/dialog/record_imports/lib.rs | 29 ++ tests/snapshots/fuzz/record_imports/lib.rs | 29 ++ .../snapshots/p3-dialog/record_exports/lib.rs | 2 + .../snapshots/p3-dialog/record_imports/lib.rs | 31 ++ tests/snapshots/p3-fuzz/record_exports/lib.rs | 2 + tests/snapshots/p3-fuzz/record_imports/lib.rs | 31 ++ tests/snapshots/record/record_exports/lib.rs | 20 + tests/snapshots/record/record_imports/lib.rs | 55 +++ tests/snapshots/replay/record_imports/lib.rs | 29 ++ 44 files changed, 1077 insertions(+), 35 deletions(-) rename tests/fixtures/{proxy-run => p2}/compose.wac (100%) rename tests/fixtures/{proxy-run => p2}/driver/Cargo.lock (100%) rename tests/fixtures/{proxy-run => p2}/driver/Cargo.toml (100%) rename tests/fixtures/{proxy-run => p2}/driver/src/lib.rs (86%) rename tests/fixtures/{proxy-run => p2}/driver/wit/world.wit (100%) rename tests/fixtures/{proxy-run => p2}/guest/Cargo.lock (100%) rename tests/fixtures/{proxy-run => p2}/guest/Cargo.toml (100%) rename tests/fixtures/{proxy-run => p2}/guest/src/lib.rs (94%) rename tests/fixtures/{proxy-run => p2}/host/Cargo.lock (100%) rename tests/fixtures/{proxy-run => p2}/host/Cargo.toml (100%) rename tests/fixtures/{proxy-run => p2}/host/src/lib.rs (93%) rename tests/fixtures/{proxy-run => p2}/host/wit/world.wit (100%) rename tests/fixtures/{proxy => p2/wit}/component.wit (100%) rename tests/fixtures/{proxy => p2/wit}/deps/guest.wit (100%) rename tests/fixtures/{proxy => p2/wit}/deps/host.wit (100%) rename tests/fixtures/{proxy => p2/wit}/deps/log@0.1.0.wit (100%) rename tests/fixtures/{proxy => p2/wit}/deps/log@0.2.0.wit (100%) create mode 100644 tests/fixtures/p3-record/guest/Cargo.lock create mode 100644 tests/fixtures/p3-record/guest/Cargo.toml create mode 100644 tests/fixtures/p3-record/guest/src/lib.rs rename tests/fixtures/{proxy-p3-run => p3}/guest/Cargo.lock (100%) rename tests/fixtures/{proxy-p3-run => p3}/guest/Cargo.toml (100%) rename tests/fixtures/{proxy-p3-run => p3}/guest/src/lib.rs (94%) rename tests/fixtures/{proxy-p3 => p3/wit}/component.wit (82%) rename tests/fixtures/{proxy-p3 => p3/wit}/deps/p3.wit (100%) diff --git a/Makefile b/Makefile index 27b4c4c..fa53fb9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-fuzz-replay test-fuzz test-dialog run-fuzz run-fuzz-replay run-record run-dialog run-viceroy +.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-record-p3 test-fuzz-replay test-fuzz test-dialog run-fuzz run-fuzz-replay run-record run-dialog run-viceroy # `make test ONE_BYTE_PAGE=1` tests with 1-byte-page memories ifeq ($(ONE_BYTE_PAGE),1) @@ -23,14 +23,14 @@ build-components: cp target/one-byte-page/wasm32-wasip2/release/debug.wasm assets/debug.one_byte_page.wasm cp target/one-byte-page/wasm32-wasip2/release/recorder.wasm assets/recorder.one_byte_page.wasm -test: test-cargo test-fuzz test-record test-fixture test-fuzz-replay test-dialog +test: test-cargo test-fuzz test-record test-fixture test-record-p3 test-fuzz-replay test-dialog # Snapshot tests of the generated sources test-cargo: cargo test -# A WASI p3 main component of tests/fixtures/proxy-p3, which uses error-context -P3_GUEST = tests/fixtures/proxy-p3-run/guest +# The WASI p3 main component of tests/fixtures/p3, which uses error-context +P3_GUEST = tests/fixtures/p3/guest P3_GUEST_OUT = target/fixture-p3 P3_GUEST_WASM = $(P3_GUEST_OUT)/wasm32-wasip2/release/proxy_p3_guest.wasm test-fuzz: @@ -57,9 +57,9 @@ test-record: $(INSTRUMENT) -m record tests/wasi_http.wasm $(INSTRUMENT) -m replay tests/wasi_http.wasm -# Records and replays the main component of tests/fixtures/proxy-run/guest, whose resources are +# Records and replays the main component of tests/fixtures/p2/guest, whose resources are # dropped by the guest and by the host, in a composition with a host and a driver (see compose.wac) -FIXTURE = tests/fixtures/proxy-run +FIXTURE = tests/fixtures/p2 FIXTURE_OUT = target/fixture FIXTURE_WASM = $(FIXTURE_OUT)/wasm32-wasip2/release test-fixture: @@ -76,11 +76,37 @@ test-fixture: $(INSTRUMENT) -m replay --use-host-recorder $(FIXTURE_WASM)/proxy_guest.wasm target/release/proxy-component run composed.wasm --invoke 'start()' --trace $(FIXTURE_OUT)/trace.out +# Records the main component of tests/fixtures/p3-record/guest, whose async imports, streams +# and futures wasmtime implements itself, and replays each trace: `run` with each pair of delays, +# RETURNED for 0, or random ones, and `cat` with stdin in one chunk, in several, or none. +P3_RECORD = tests/fixtures/p3-record/guest +P3_RECORD_OUT = target/fixture-p3-record +P3_RECORD_WASM = $(P3_RECORD_OUT)/wasm32-wasip2/release/proxy_p3_record_guest.wasm +test-record-p3: + cargo build --manifest-path $(P3_RECORD)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_RECORD_OUT) + $(INSTRUMENT) -m record $(P3_RECORD_WASM) + cp composed.wasm $(P3_RECORD_OUT)/record.wasm + $(INSTRUMENT) -m replay $(P3_RECORD_WASM) + cp composed.wasm $(P3_RECORD_OUT)/replay.wasm + for delays in "5 15" "15 5" "0 10" "10 0" "0 0" "5 5" random random random random random; do \ + set -- $$delays; env=""; [ "$$1" = random ] || env="--env DELAY_A=$$1 --env DELAY_B=$$2"; \ + $(WASMTIME) $$env --invoke 'run()' $(P3_RECORD_OUT)/record.wasm > $(P3_RECORD_OUT)/trace.out 2>&1 || { \ + cat $(P3_RECORD_OUT)/trace.out; echo "recording failed, with the output above"; exit 1; }; \ + $(WASMTIME) --invoke 'start()' $(P3_RECORD_OUT)/replay.wasm < $(P3_RECORD_OUT)/trace.out > $(P3_RECORD_OUT)/replay.out 2>&1 || { \ + cat $(P3_RECORD_OUT)/replay.out; echo "replay failed, with the output above, of the trace in $(P3_RECORD_OUT)/trace.out"; exit 1; }; \ + done + for input in "printf ''" "printf first; sleep 0.05; printf second" "for i in 1 2 3 4 5; do echo line \$$i; sleep 0.01; done" "head -c 10000 /dev/zero"; do \ + sh -c "$$input" | $(WASMTIME) --invoke 'cat()' $(P3_RECORD_OUT)/record.wasm > $(P3_RECORD_OUT)/trace.out 2> $(P3_RECORD_OUT)/stderr.out || { \ + cat $(P3_RECORD_OUT)/trace.out $(P3_RECORD_OUT)/stderr.out; echo "recording failed, with the output above"; exit 1; }; \ + $(WASMTIME) --invoke 'start()' $(P3_RECORD_OUT)/replay.wasm < $(P3_RECORD_OUT)/trace.out > $(P3_RECORD_OUT)/replay.out 2>&1 || { \ + cat $(P3_RECORD_OUT)/replay.out; echo "replay failed, with the output above, of the trace in $(P3_RECORD_OUT)/trace.out"; exit 1; }; \ + done + # The spike's guest, which joins two concurrent results in the order its wakeups receive them RACE = tests/fixtures/spike-p3-race RACE_OUT = target/fixture-race RACE_WASM = $(RACE_OUT)/wasm32-wasip2/release/race_guest.wasm -# Fuzzes the main components of tests/fixtures/proxy-run/guest, the p3 fixture, wasi_http_p3 and +# Fuzzes the main components of tests/fixtures/p2/guest, the p3 fixture, wasi_http_p3 and # the spike's guest, and replays each trace it writes, which ends as the fuzzing run did: to the # end, or in the main component's trap, e.g. a guest's panic on a fuzzed error. test-fuzz-replay: diff --git a/README.md b/README.md index 5e1fe82..e06753a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To build the CLI tool, just run `make all`. To test record and fuzzing, run `make test`. `cargo test` checks the generated sources against the snapshots in `tests/snapshots`, generated -from the WIT in `tests/fixtures/proxy`. After an intended change, run `BLESS=1 cargo test` and +from the WIT in `tests/fixtures/p2/wit` and `tests/fixtures/p3/wit`. After an intended change, run `BLESS=1 cargo test` and review the snapshots' diff. ## Usage diff --git a/assets/executor.rs b/assets/executor.rs index 75dfb4e..77c882b 100644 --- a/assets/executor.rs +++ b/assets/executor.rs @@ -47,7 +47,9 @@ // arrival of an export call, which the exports component counts with the `step` export. Host // lines delivered outside a call carry the step, `at`, in which they happened (see // `docs/p3-record-replay.md`, Steps). A job that waits for a later run, e.g. an async import -// the fuzzer made STARTED, makes the executor wake itself, as a host completion would. +// the fuzzer made STARTED, makes the executor wake itself, as a host completion would. A run +// after it, as after a host completion, takes an event that came meanwhile (see +// `executor_callback`), so that a step means the same however the executor was woken. type Job = std::pin::Pin>>; #[derive(Default)] struct Executor { @@ -65,6 +67,10 @@ thread_local! { // `wait_for_run`), and whether it was woken meanwhile. static HELD: std::cell::Cell = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD: std::cell::Cell = const { std::cell::Cell::new(false) }; + // Set by `executor_callback` when the executor resumes after a yield, for its next poll to + // be a probe, and whether a probe was the last poll. + static PROBE: std::cell::Cell = const { std::cell::Cell::new(false) }; + static PROBED: std::cell::Cell = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. #[allow(dead_code)] @@ -231,6 +237,14 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + // A probe isn't a run: it wakes the executor, for wit-bindgen to take an event that came + // while it yielded, before its run (see `executor_callback`). + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); // Held, it isn't a run: the jobs are polled at the next, as every run polls them all. if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); @@ -264,6 +278,25 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { wit_bindgen::rt::async_support::callback(event0, event1, event2) }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index e20be2c..28e6106 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -27,8 +27,8 @@ dialog support for p3. for p2 components. - **Replay** of p2 and p3 traces ([Replay](#replay)): calls and steps, RETURNED and STARTED, streams, futures and error-contexts. -- **Record mode for p3**: async imports, RETURNED or STARTED, with their steps. Streams and - futures are next. +- **Record mode for p3**: async imports, RETURNED or STARTED, with their steps, and streams and + futures, through pumps. ## Where nondeterminism comes from @@ -215,7 +215,7 @@ number in that table, e.g. `#fields-3` or `#stream-7`. - **Streams and futures** are labeled by the handle of the end the proxy received, which it keeps for the stream's lifetime: the host's reader in an import's result or an export's arguments, the guest's in an import's arguments or an export's result. The proxy pumps it through a pair of its - own, whose ends never appear in the trace. A stream or future first appears in the value that + own, whose writer never appears in the trace. A stream or future first appears in the value that brings it across, in the table of the component that call or result crosses, and every later line uses the same label: @@ -225,8 +225,9 @@ number in that table, e.g. `#fields-3` or `#stream-7`. | host | | `Return { id: c4, value: Some("(#stream-6, #future-9)") }` | | host | 5 | `Write { stream: #stream-6, items: "[52]" }` | - - **Record mode** prints each value as it received it, before `to_proxy`. Today it prints - arguments after converting them, which has to change. + - **Record mode** prints a call's arguments converted, and its result as received, as it does + for every value. So the reader it hands on in the arguments is printed by the received end's + label too, which it registers with wasm-wave's `set_handle_label`, as fuzz mode does. - **Fuzz and dialog modes** make the stream themselves: they keep the writer, and hand the reader out, which frees the reader's slot. So they label the stream by the writer, also where the reader is printed, in the value that hands it out: they register the pair with wasm-wave's @@ -305,7 +306,10 @@ executor task, like fuzz mode's: would then go between the host and the guest without it. It keeps the end it received, creates a pair of its own, and passes on that pair's other end. A pump is the executor job between the two: it reads from one end, writes what it read to the other, and records each copy, and passes - on a drop the same way. A future is pumped once, for its value. + on a drop the same way. It reads from the writer only once the reader reads, without + [read-ahead](#read-ahead-in-pumps): a zero-length write to the reader's end waits for that, as + it completes once the reader reads, and leaves the read pending. A future is pumped once, for + its value. So nothing the host does reaches the guest outside an executor run. That's why a step number can mark when each host event happened. @@ -377,6 +381,10 @@ Replay has to know when to perform each host line. There are three kinds of line to it. In replay, the executor counts its steps the same way, and a job holding such a line waits until the count reaches `n`. There is no host to wake the executor, so a job waiting for a later step makes the executor wake itself, by yielding, and its next run is the next step. + That run first takes an event that came while it yielded, e.g. a guest's write, as a waiting + executor woken by the event does in the recording: wit-bindgen would only take it after a + poll, in another run. So the executor's callback, which wraps wit-bindgen's, makes the first + poll after a yield a probe, which only lets wit-bindgen take a pending event. A task that waits for a step from outside the executor, e.g. the mock of a STARTED call, holds the executor from the run it waited for until the task resumes: otherwise a run, e.g. for a guest's drop, could come in between, which replay, knowing only the step, would place @@ -494,7 +502,13 @@ or blocks first. the guest only once the host reads. Each of the guest's copies blocks first, so a guest copy that completes at once is never recorded, nor is whatever the guest does differently after one. The two copies of a chunk, between the host and the pump, and between the pump and the guest, - happen together, so one line describes both. + happen together, so one line describes both. This is record mode's choice: + - A zero-length write to the reader's end tells the pump that the reader reads, but not how + much: the pump reads up to its own capacity, 4096 items, and the reader's reads may split the + chunk, which replay's writes split alike. + - A host's end may answer a zero-length copy at once, without being ready, as the component + model allows it; wasmtime's do. The pump then takes a chunk of the guest's as soon as the + guest writes it, which a host that isn't ready, e.g. under backpressure, wouldn't have. - **With read-ahead**, the pump keeps a read pending on the host's side, and data ready for the guest, or the other way, for a stream the guest writes. Immediate copies become reachable, and every copy the guest makes is still recorded. What changes is what the host sees: it is read @@ -700,10 +714,11 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla 3. **Replay**, tested on traces that fuzz mode writes, with the checks of [Correctness](#checking-the-implementation): done. It replays 100 of 100 fuzz traces of the p3 fixture, and 60 of 60 of `wasi_http_p3`. -4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. Async imports - are done: `make test-record-p3` records `tests/fixtures/proxy-p3-record` under `wasmtime run`, - with delays that make its calls RETURNED or STARTED, and replays each trace. Next are streams - and futures, through pumps. +4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. Async imports, + streams and futures are done: `make test-record-p3` records `tests/fixtures/p3-record` + under `wasmtime run`, with delays that make its calls RETURNED or STARTED, and stdin copied to + stderr through their streams, and replays each trace. Next are the hosts: `wasmtime serve`, + and `proxy-component run`. 5. **Portability across runtimes**: the profile proposal, or the shim. ## Open questions @@ -713,7 +728,7 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla steps places them exactly. 2. **[Read-ahead in pumps](#read-ahead-in-pumps)**: whether to read ahead, which makes the guest's immediate copies reachable, at the cost of the host's view, and of copy lines that - record the host's chunks rather than the guest's. + record the host's chunks rather than the guest's. Record mode doesn't, for now. 3. **`list`** as WAVE lists or byte strings. 4. **Which way to portability**: the profile, the shim, or both. 5. **Hosts that are components**, e.g. virtualized WASI composed with the main component: whether diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 1e50063..fc57e07 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -668,9 +668,21 @@ fn export_call_defs(mode: &Mode) -> Vec { /// Helpers of record mode, which forwards the main component's calls to the host, and the host's /// to it, for each component. fn record_defs(has_executor: Option) -> Vec { + let common: File = parse_quote! { + thread_local! { + // Who sends the values being converted, for a stream's or future's pump (see + // `pump_defs`): the caller for the arguments, the callee for the result. + static SENDER: std::cell::Cell = const { std::cell::Cell::new(Party::Host) }; + } + }; let file: File = match has_executor { - // The imports component, whose executor awaits the host's async calls. + // The imports component, whose executor awaits the host's async calls, and runs the + // pumps, which record in the step of its run. Some(HasExecutor::Exports) => parse_quote! { + #[allow(dead_code)] + const TRACE_TABLE: Table = Table::Imports; + #[allow(dead_code)] + fn trace_at() -> u32 { current_step() } /// The host's result of an async call: at once, if it is ready on the first poll, so /// that the guest sees RETURNED, and otherwise from an executor job, which awaits the /// host's subtask, with the step of the run it completed in, as replay returns it in @@ -691,8 +703,13 @@ fn record_defs(has_executor: Option) -> Vec { } }, // The exports component, which runs the imports component's executor while the host's - // calls of async exports are pending: there is no `start` to run it. + // calls of async exports are pending: there is no `start` to run it. Its pumps run on + // the task of the call, and record in the last step, as fuzz mode's jobs there do. Some(HasExecutor::Imports) => parse_quote! { + #[allow(dead_code)] + const TRACE_TABLE: Table = Table::Exports; + #[allow(dead_code)] + fn trace_at() -> u32 { proxy::conversion::executor::current_step() } thread_local! { // The host's calls of async exports that haven't returned. static PENDING_CALLS: std::cell::Cell = const { std::cell::Cell::new(0) }; @@ -713,7 +730,7 @@ fn record_defs(has_executor: Option) -> Vec { }, None => return Vec::new(), }; - file.items + common.items.into_iter().chain(file.items).collect() } /// Sends the panics of the component to the recorder, which reports them (see `fail`), in @@ -812,6 +829,39 @@ fn start_spawn_defs() -> Vec { file.items } +/// The bindings of a generated component, with the callback of the executor's task, if it +/// exports `proxy:conversion/executor`, replaced by `executor_callback` (see +/// `assets/executor.rs`), which wraps wit-bindgen's. +/// +/// Panics if the bindings don't have wit-bindgen's callback of `run` where it is expected. +pub fn patch_bindings(bindings: String) -> String { + const EXPORT: &str = "[callback][async-lift]proxy:conversion/executor#run"; + const CALLBACK: &str = "pub unsafe fn __callback_run("; + const BODY: &str = "wit_bindgen::rt::async_support::callback(event0, event1, event2)"; + if !bindings.contains(EXPORT) { + return bindings; + } + // The `executor` module's, which has no module of its own, so it ends before the next one. + let callbacks: Vec = bindings + .match_indices("pub mod executor {") + .filter_map(|(start, _)| { + let module = &bindings[start..]; + let end = module[1..].find("pub mod ").map_or(module.len(), |end| end + 1); + module[..end].find(CALLBACK).map(|callback| start + callback) + }) + .collect(); + let [callback] = callbacks[..] else { + panic!("expected one `__callback_run` of the executor in the bindings, found {callbacks:?}"); + }; + let body = bindings[callback..] + .find(BODY) + .map(|body| callback + body) + .expect("expected wit-bindgen's callback in the executor's `__callback_run`"); + let mut patched = bindings; + patched.replace_range(body..body + BODY.len(), "crate::executor_callback(event0, event1, event2)"); + patched +} + pub fn get_proxy_path(src_path: &[String]) -> Vec { assert!(src_path.len() >= 3); let mut res = src_path.to_vec(); diff --git a/src/codegen/record.rs b/src/codegen/record.rs index 9989edc..afdeba2 100644 --- a/src/codegen/record.rs +++ b/src/codegen/record.rs @@ -91,6 +91,7 @@ impl State { let (res, __at) = #call; let wave_res = wasm_wave::to_string(&res.to_value()).unwrap(); proxy::recorder::record::record(&Line { at: __at, ..trace_return(Party::#callee, __id, Some(wave_res)) }); + SENDER.set(Party::#callee); #res } }; @@ -98,6 +99,7 @@ impl State { #sig { let __id = trace_task_id(Table::#table); let #is_mut params: Vec = #init_vec; + SENDER.set(Party::#caller); #( let #arg_names = #arg_names.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); diff --git a/src/instrument.rs b/src/instrument.rs index c279613..0b3d61c 100644 --- a/src/instrument.rs +++ b/src/instrument.rs @@ -159,7 +159,7 @@ fn bindgen( let out_dir = tmp_dir.join(dest_name); fs::create_dir_all(&out_dir)?; let (mut resolve, world) = parse_wit(wit_dir, Some(world_name))?; - let bindings = crate::wit_util::generate_bindings(&mut resolve, world)?; + let bindings = codegen::patch_bindings(crate::wit_util::generate_bindings(&mut resolve, world)?); fs::write(out_dir.join("bindings.rs"), &bindings)?; let lib = codegen::generate( &args.mode, diff --git a/src/tests/codegen.rs b/src/tests/codegen.rs index 344220e..9e1aab8 100644 --- a/src/tests/codegen.rs +++ b/src/tests/codegen.rs @@ -1,14 +1,15 @@ //! Snapshot tests of the sources generated from the WIT of a main component in -//! `tests/fixtures/proxy`, which compare them with `tests/snapshots/`. +//! `tests/fixtures/p2/wit` and `tests/fixtures/p3/wit`, which compare them with +//! `tests/snapshots/`. //! `BLESS=1 cargo test` rewrites the snapshots. `make test` builds and runs generated sources. use crate::Mode; use crate::instrument::{InstrumentArgs, generate_sources}; use std::fs; use std::path::{Path, PathBuf}; -const FIXTURE: &str = "tests/fixtures/proxy"; +const FIXTURE: &str = "tests/fixtures/p2/wit"; /// A WASI p3 main component, with async functions, streams, futures and error-contexts. -const P3_FIXTURE: &str = "tests/fixtures/proxy-p3"; +const P3_FIXTURE: &str = "tests/fixtures/p3/wit"; const SNAPSHOTS: &str = "tests/snapshots"; /// The generated files to compare, relative to the generation directory. const OUTPUTS: &[&str] = &[ diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 45eea80..1bd993e 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -85,6 +85,7 @@ fn host_end_defs(has_streams: bool, has_futures: bool) -> Vec { if has_streams { let file: syn::File = syn::parse_quote! { /// A stream the host writes: its writer, its reader, to hand out, and its label. + #[allow(dead_code)] fn host_stream( label_reader: bool, ) -> (wit_bindgen::StreamWriter, wit_bindgen::StreamReader, String) { @@ -102,6 +103,7 @@ fn host_end_defs(has_streams: bool, has_futures: bool) -> Vec { let file: syn::File = syn::parse_quote! { /// A future the host writes, which it always does: its writer, its reader, to hand /// out, and its label. + #[allow(dead_code)] fn host_future( label_reader: bool, ) -> (wit_bindgen::FutureWriter, wit_bindgen::FutureReader, String) { diff --git a/src/traits/proxy.rs b/src/traits/proxy.rs index cf00b73..0c60ab3 100644 --- a/src/traits/proxy.rs +++ b/src/traits/proxy.rs @@ -353,9 +353,183 @@ impl Trait for ProxyTrait<'_> { for n in 1..=9 { items.extend(tuple_trait(n)); } + items.extend(pump_defs(self.state.has_streams, self.state.has_futures)); items } } +/// Record mode's pumps: a stream or future the proxy receives, e.g. the host's in an import's +/// result, isn't passed on, as the copies would then go between the host and the guest without +/// it. Its conversion makes a pair of the other side's type, hands on its reader, and spawns a +/// job, the pump, which copies from the end it received to the pair's writer, and records each +/// copy, and each side's drop. The stream is labeled by the end the proxy received, which the pump +/// keeps for its lifetime. `SENDER` says who sent it. +/// +/// A call's arguments are printed converted, and its result as received, so the reader handed +/// on in the arguments is printed by the received end's label too, which `label_by_writer` +/// registers, and so are the items of a stream or future the caller sends. +fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { + let mut items = crate::traits::host_end_defs(has_streams, has_futures); + if !has_streams && !has_futures { + return items; + } + let file: File = parse_quote! { + /// The other party. + fn trace_receiver(sender: Party) -> Party { + match sender { + Party::Host => Party::Guest, + Party::Guest => Party::Host, + } + } + /// A line of `by`, which has a step if it is the host's. + fn pump_line(by: Party, at: u32, event: Event) -> Line { + let at = matches!(by, Party::Host).then_some(at); + Line { by, at, event } + } + /// Whether the caller of the call sends the value, rather than the callee: the guest's + /// imports component, and the host's exports component, which call. + fn sent_by_caller(sender: Party) -> bool { + matches!(TRACE_TABLE, Table::Imports) == matches!(sender, Party::Guest) + } + }; + items.extend(file.items); + if has_streams { + let file: File = parse_quote! { + impl ToProxy for wit_bindgen::StreamReader + where + T: ToProxy + ToValue + wit_stream::StreamPayload + 'static, + T::Output: ToValue + wit_stream::StreamPayload + 'static, + { + type Output = wit_bindgen::StreamReader; + fn to_proxy(self) -> Self::Output { + let (writer, reader) = wit_stream::new::(); + let sender = SENDER.get(); + if sent_by_caller(sender) { + label_by_writer(reader.handle(), self.handle()); + } + let label = Label { table: TRACE_TABLE, name: handle_label(&self) }; + spawn(pump_stream(self, writer, label, sender)); + reader + } + } + /// Copies the items of a stream from `from`, which `sender` writes, to `to`, which + /// the other party reads: once the reader reads, so that the writer is read only + /// then, as it would be without the proxy. A zero-length write waits for that, as + /// it completes once the reader reads. A host's end may answer it at once, without + /// being ready, e.g. wasmtime's, which then takes a chunk of the guest's early. + async fn pump_stream( + mut from: wit_bindgen::StreamReader, + mut to: wit_bindgen::StreamWriter, + label: Label, + sender: Party, + ) where + T: ToProxy + ToValue + wit_stream::StreamPayload, + U: ToValue + wit_stream::StreamPayload, + { + let receiver = trace_receiver(sender); + let wave = |item: &dyn ToValue| wasm_wave::to_string(&item.to_value()).unwrap(); + loop { + let (result, _) = to.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = result { + let line = pump_line(receiver, trace_at(), Event::Drop(label)); + return proxy::recorder::record::record(&line); + } + // The host's side of a copy is its read of the guest's items, or its write of + // its own, in whose step the line is. + let read_at = trace_at(); + let (result, items) = from.read(Vec::with_capacity(4096)).await; + if items.is_empty() { + if let wit_bindgen::StreamResult::Dropped = result { + let line = pump_line(sender, trace_at(), Event::Drop(label)); + return proxy::recorder::record::record(&line); + } + continue; + } + let received: Vec = items.iter().map(|item| wave(item)).collect(); + let items: Vec = items.into_iter().map(ToProxy::to_proxy).collect(); + let waves = if sent_by_caller(sender) { + items.iter().map(|item| wave(item)).collect() + } else { + received + }; + let write_at = trace_at(); + let rest = to.write_all(items).await; + let written = waves.len() - rest.len(); + if written > 0 { + let chunk = Chunk { + stream: label.clone(), + items: format!("[{}]", waves[..written].join(", ")), + }; + let (at, event) = match sender { + Party::Host => (write_at, Event::Write(chunk)), + Party::Guest => (read_at, Event::Read(chunk)), + }; + proxy::recorder::record::record(&pump_line(Party::Host, at, event)); + } + if !rest.is_empty() { + let line = pump_line(receiver, trace_at(), Event::Drop(label)); + return proxy::recorder::record::record(&line); + } + if let wit_bindgen::StreamResult::Dropped = result { + let line = pump_line(sender, trace_at(), Event::Drop(label)); + return proxy::recorder::record::record(&line); + } + } + } + }; + items.extend(file.items); + } + if has_futures { + let file: File = parse_quote! { + impl ToProxy for wit_bindgen::FutureReader + where + T: ToProxy + ToValue + wit_future::FuturePayload + 'static, + T::Output: ToValue + wit_future::FuturePayload + 'static, + { + type Output = wit_bindgen::FutureReader; + fn to_proxy(self) -> Self::Output { + // The pump always writes the value, unless the reader is dropped first. + let (writer, reader) = + wit_future::new::(|| unreachable!("the value is always written")); + let sender = SENDER.get(); + if sent_by_caller(sender) { + label_by_writer(reader.handle(), self.handle()); + } + let label = Label { table: TRACE_TABLE, name: handle_label(&self) }; + spawn(pump_future(self, writer, label, sender)); + reader + } + } + /// Passes the value of a future from `from`, which `sender` writes, to `to`, which + /// the other party reads, unless it drops it first. + async fn pump_future( + from: wit_bindgen::FutureReader, + to: wit_bindgen::FutureWriter, + label: Label, + sender: Party, + ) where + T: ToProxy + ToValue + wit_future::FuturePayload, + U: ToValue + wit_future::FuturePayload, + { + let value = from.await; + let received = wasm_wave::to_string(&value.to_value()).unwrap(); + let value = value.to_proxy(); + let wave = if sent_by_caller(sender) { + wasm_wave::to_string(&value.to_value()).unwrap() + } else { + received + }; + let at = trace_at(); + let line = match to.write(value).await { + Ok(()) => pump_line(sender, at, Event::Value(FutureValue { future: label, value: wave })), + Err(_) => pump_line(trace_receiver(sender), trace_at(), Event::Drop(label)), + }; + proxy::recorder::record::record(&line); + } + }; + items.extend(file.items); + } + items +} // Overrides `ToProxy::cast` for impls where `Output == Self`. fn identity_cast() -> TokenStream { quote! { diff --git a/tests/fixtures/proxy-run/compose.wac b/tests/fixtures/p2/compose.wac similarity index 100% rename from tests/fixtures/proxy-run/compose.wac rename to tests/fixtures/p2/compose.wac diff --git a/tests/fixtures/proxy-run/driver/Cargo.lock b/tests/fixtures/p2/driver/Cargo.lock similarity index 100% rename from tests/fixtures/proxy-run/driver/Cargo.lock rename to tests/fixtures/p2/driver/Cargo.lock diff --git a/tests/fixtures/proxy-run/driver/Cargo.toml b/tests/fixtures/p2/driver/Cargo.toml similarity index 100% rename from tests/fixtures/proxy-run/driver/Cargo.toml rename to tests/fixtures/p2/driver/Cargo.toml diff --git a/tests/fixtures/proxy-run/driver/src/lib.rs b/tests/fixtures/p2/driver/src/lib.rs similarity index 86% rename from tests/fixtures/proxy-run/driver/src/lib.rs rename to tests/fixtures/p2/driver/src/lib.rs index c1eff61..33e8dc7 100644 --- a/tests/fixtures/proxy-run/driver/src/lib.rs +++ b/tests/fixtures/p2/driver/src/lib.rs @@ -1,7 +1,7 @@ //! Plays the host of the fixture guest: makes sessions, gives one a counter, and drops them. mod bindings { wit_bindgen::generate!({ - path: ["../../proxy/deps/host.wit", "../../proxy/deps/guest.wit", "wit"], + path: ["../wit/deps/host.wit", "../wit/deps/guest.wit", "wit"], world: "test:driver/driver", generate_all, }); diff --git a/tests/fixtures/proxy-run/driver/wit/world.wit b/tests/fixtures/p2/driver/wit/world.wit similarity index 100% rename from tests/fixtures/proxy-run/driver/wit/world.wit rename to tests/fixtures/p2/driver/wit/world.wit diff --git a/tests/fixtures/proxy-run/guest/Cargo.lock b/tests/fixtures/p2/guest/Cargo.lock similarity index 100% rename from tests/fixtures/proxy-run/guest/Cargo.lock rename to tests/fixtures/p2/guest/Cargo.lock diff --git a/tests/fixtures/proxy-run/guest/Cargo.toml b/tests/fixtures/p2/guest/Cargo.toml similarity index 100% rename from tests/fixtures/proxy-run/guest/Cargo.toml rename to tests/fixtures/p2/guest/Cargo.toml diff --git a/tests/fixtures/proxy-run/guest/src/lib.rs b/tests/fixtures/p2/guest/src/lib.rs similarity index 94% rename from tests/fixtures/proxy-run/guest/src/lib.rs rename to tests/fixtures/p2/guest/src/lib.rs index 13b0c0d..dfb5853 100644 --- a/tests/fixtures/proxy-run/guest/src/lib.rs +++ b/tests/fixtures/p2/guest/src/lib.rs @@ -1,4 +1,4 @@ -//! A main component for the WIT in `tests/fixtures/proxy`, which drops and moves resources both +//! A main component for the WIT in `tests/fixtures/p2/wit`, which drops and moves resources both //! ways, to test that record mode records exactly the drops, and replay replays them. `make //! test-fixture` runs it with `../host` and `../driver`, see `../compose.wac`: //! - `new session` drops a counter it made, and moves another to the host with `merge`; @@ -9,7 +9,7 @@ //! - dropping a session logs, then drops its counter: imports inside an export's drop. mod bindings { wit_bindgen::generate!({ - path: "../../proxy", + path: "../wit", world: "root", generate_all, }); diff --git a/tests/fixtures/proxy-run/host/Cargo.lock b/tests/fixtures/p2/host/Cargo.lock similarity index 100% rename from tests/fixtures/proxy-run/host/Cargo.lock rename to tests/fixtures/p2/host/Cargo.lock diff --git a/tests/fixtures/proxy-run/host/Cargo.toml b/tests/fixtures/p2/host/Cargo.toml similarity index 100% rename from tests/fixtures/proxy-run/host/Cargo.toml rename to tests/fixtures/p2/host/Cargo.toml diff --git a/tests/fixtures/proxy-run/host/src/lib.rs b/tests/fixtures/p2/host/src/lib.rs similarity index 93% rename from tests/fixtures/proxy-run/host/src/lib.rs rename to tests/fixtures/p2/host/src/lib.rs index cce3d28..bcf2686 100644 --- a/tests/fixtures/proxy-run/host/src/lib.rs +++ b/tests/fixtures/p2/host/src/lib.rs @@ -1,7 +1,7 @@ //! A host for the fixture guest: counters that count, and a log that prints. mod bindings { wit_bindgen::generate!({ - path: ["../../proxy/deps/host.wit", "../../proxy/deps/log@0.1.0.wit", "wit"], + path: ["../wit/deps/host.wit", "../wit/deps/log@0.1.0.wit", "wit"], world: "test:mockhost/mockhost", generate_all, }); diff --git a/tests/fixtures/proxy-run/host/wit/world.wit b/tests/fixtures/p2/host/wit/world.wit similarity index 100% rename from tests/fixtures/proxy-run/host/wit/world.wit rename to tests/fixtures/p2/host/wit/world.wit diff --git a/tests/fixtures/proxy/component.wit b/tests/fixtures/p2/wit/component.wit similarity index 100% rename from tests/fixtures/proxy/component.wit rename to tests/fixtures/p2/wit/component.wit diff --git a/tests/fixtures/proxy/deps/guest.wit b/tests/fixtures/p2/wit/deps/guest.wit similarity index 100% rename from tests/fixtures/proxy/deps/guest.wit rename to tests/fixtures/p2/wit/deps/guest.wit diff --git a/tests/fixtures/proxy/deps/host.wit b/tests/fixtures/p2/wit/deps/host.wit similarity index 100% rename from tests/fixtures/proxy/deps/host.wit rename to tests/fixtures/p2/wit/deps/host.wit diff --git a/tests/fixtures/proxy/deps/log@0.1.0.wit b/tests/fixtures/p2/wit/deps/log@0.1.0.wit similarity index 100% rename from tests/fixtures/proxy/deps/log@0.1.0.wit rename to tests/fixtures/p2/wit/deps/log@0.1.0.wit diff --git a/tests/fixtures/proxy/deps/log@0.2.0.wit b/tests/fixtures/p2/wit/deps/log@0.2.0.wit similarity index 100% rename from tests/fixtures/proxy/deps/log@0.2.0.wit rename to tests/fixtures/p2/wit/deps/log@0.2.0.wit diff --git a/tests/fixtures/p3-record/guest/Cargo.lock b/tests/fixtures/p3-record/guest/Cargo.lock new file mode 100644 index 0000000..d041791 --- /dev/null +++ b/tests/fixtures/p3-record/guest/Cargo.lock @@ -0,0 +1,410 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "macro-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxy-p3-record-guest" +version = "0.1.0" +dependencies = [ + "wasip3", + "wit-bindgen", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "wasip3" +version = "0.10.0+wasi-0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1657e9f48c63d6c3f143f70d5c6e2be7d463371a7af1ed939a5b83d3ca080d0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4495a6f85dd94c411c749303efcbc5defade0b0cbdbc0541495241ab917a9b5a" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86ff6668190d9fb70372b306b4f9fe8930f0e2f57ce72e4922bf2f117900949" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974d643b125b272607e2fff8012ea14a8328acd7621e4bafa7afb32500052b1b" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06" +dependencies = [ + "bitflags", + "futures", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e122e390603abfacd548027016cd0113a6e2bf2f8ce6bdd3658b013456ea8" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e26e13b9d972026778d2ffb7985736de116026fdc8d9464f966e12df2eb240" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a008a0d6b4840677326beb4edbeb210a2f1b7d0183fb7cc33f466b90fd7fb6" +dependencies = [ + "anyhow", + "macro-string", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078def49fdb49cab405c3006e05793b3fdaf7069126ba6b9c97d01ec8c98fe4c" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f0dc051466ffe46787721fca3d088536f158ae400be044f1b1c386ba9397ad" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/tests/fixtures/p3-record/guest/Cargo.toml b/tests/fixtures/p3-record/guest/Cargo.toml new file mode 100644 index 0000000..938dae3 --- /dev/null +++ b/tests/fixtures/p3-record/guest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "proxy-p3-record-guest" +version = "0.1.0" +edition = "2024" +publish = false + +[workspace] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { version = "0.63.0", features = ["async-spawn"] } +wasip3 = "0.10.0" diff --git a/tests/fixtures/p3-record/guest/src/lib.rs b/tests/fixtures/p3-record/guest/src/lib.rs new file mode 100644 index 0000000..6676559 --- /dev/null +++ b/tests/fixtures/p3-record/guest/src/lib.rs @@ -0,0 +1,87 @@ +//! A WASI p3 main component for record mode, whose async imports, streams and futures wasmtime +//! implements itself: `run` waits for `a` and `b` concurrently, with `wait-for`, and joins them in +//! the order its wakeups receive them, as the spike's guest does (see +//! tests/fixtures/spike-p3-race); `cat` copies stdin to stderr, through their streams. +wit_bindgen::generate!({ + inline: r#" + package test:rec@0.1.0; + interface api { + run: async func() -> string; + cat: async func() -> string; + } + world root { + export api; + } + "#, +}); + +use std::future::Future; +use std::task::Poll; + +/// Waits `DELAY_` milliseconds, or a random 0-19, and returns the key. WASI 0.3's +/// environment, a sync function that doesn't suspend, unlike WASI 0.2's. +async fn wait(key: &str) -> String { + let name = format!("DELAY_{}", key.to_uppercase()); + let delay = wasip3::cli::environment::get_environment() + .into_iter() + .find(|(n, _)| *n == name) + .and_then(|(_, delay)| delay.parse::().ok()) + .unwrap_or_else(|| u64::from(wasip3::random::random::get_random_bytes(1)[0] % 20)); + wasip3::clocks::monotonic_clock::wait_for(delay * 1_000_000).await; + key.to_uppercase() +} + +struct Guest; + +impl exports::test::rec::api::Guest for Guest { + async fn run() -> String { + let mut a = Box::pin(wait("a")); + let mut b = Box::pin(wait("b")); + let (mut a_done, mut b_done) = (false, false); + let mut out = String::new(); + // What each wakeup received, returned with the result rather than printed: printing is a + // sync call to the host, which can suspend the guest and change the schedule. + let mut wakeups = Vec::new(); + // Both are polled on each wakeup, `a` first: if both are ready, `a` comes first. + std::future::poll_fn(|cx| { + let mut got = String::new(); + if !a_done && let Poll::Ready(value) = a.as_mut().poll(cx) { + got += &value; + a_done = true; + } + if !b_done && let Poll::Ready(value) = b.as_mut().poll(cx) { + got += &value; + b_done = true; + } + if !got.is_empty() { + wakeups.push(got.clone()); + } + out += &got; + if a_done && b_done { Poll::Ready(()) } else { Poll::Pending } + }) + .await; + format!("{out} (wakeups: {})", wakeups.join(" ")) + } + async fn cat() -> String { + let (mut stdin, stdin_done) = wasip3::cli::stdin::read_via_stream(); + let (mut writer, reader) = wasip3::wit_stream::new::(); + let stderr_done = wasip3::cli::stderr::write_via_stream(reader); + let (mut bytes, mut reads) = (0, 0); + loop { + let (result, buf) = stdin.read(Vec::with_capacity(16)).await; + if !buf.is_empty() { + bytes += buf.len(); + reads += 1; + writer.write_all(buf).await; + } + if let wit_bindgen::StreamResult::Dropped = result { + break; + } + } + drop(writer); + let (stdin_done, stderr_done) = (stdin_done.await, stderr_done.await); + format!("{bytes} bytes in {reads} reads ({stdin_done:?}, {stderr_done:?})") + } +} + +export!(Guest); diff --git a/tests/fixtures/proxy-p3-run/guest/Cargo.lock b/tests/fixtures/p3/guest/Cargo.lock similarity index 100% rename from tests/fixtures/proxy-p3-run/guest/Cargo.lock rename to tests/fixtures/p3/guest/Cargo.lock diff --git a/tests/fixtures/proxy-p3-run/guest/Cargo.toml b/tests/fixtures/p3/guest/Cargo.toml similarity index 100% rename from tests/fixtures/proxy-p3-run/guest/Cargo.toml rename to tests/fixtures/p3/guest/Cargo.toml diff --git a/tests/fixtures/proxy-p3-run/guest/src/lib.rs b/tests/fixtures/p3/guest/src/lib.rs similarity index 94% rename from tests/fixtures/proxy-p3-run/guest/src/lib.rs rename to tests/fixtures/p3/guest/src/lib.rs index 3984d58..5fa3775 100644 --- a/tests/fixtures/proxy-p3-run/guest/src/lib.rs +++ b/tests/fixtures/p3/guest/src/lib.rs @@ -1,8 +1,8 @@ -//! A main component of `tests/fixtures/proxy-p3`, which `make test-fuzz` fuzzes: it uses each +//! A main component of `tests/fixtures/p3/wit`, which `make test-fuzz` fuzzes: it uses each //! kind of async function, stream, future and error-context of the WIT. It tolerates whatever a //! host may do, e.g. stop reading a stream it returned, so that a fuzzed run doesn't trap. wit_bindgen::generate!({ - path: "../../proxy-p3", + path: "../wit", world: "root", generate_all, }); diff --git a/tests/fixtures/proxy-p3/component.wit b/tests/fixtures/p3/wit/component.wit similarity index 82% rename from tests/fixtures/proxy-p3/component.wit rename to tests/fixtures/p3/wit/component.wit index c73f0a3..a57f310 100644 --- a/tests/fixtures/proxy-p3/component.wit +++ b/tests/fixtures/p3/wit/component.wit @@ -1,4 +1,4 @@ -// The WIT of a WASI p3 main component, as `extract_wit` writes it; see `tests/fixtures/proxy`. +// The WIT of a WASI p3 main component, as `extract_wit` writes it; see `tests/fixtures/p2/wit`. package root:component; world root { diff --git a/tests/fixtures/proxy-p3/deps/p3.wit b/tests/fixtures/p3/wit/deps/p3.wit similarity index 100% rename from tests/fixtures/proxy-p3/deps/p3.wit rename to tests/fixtures/p3/wit/deps/p3.wit diff --git a/tests/snapshots/dialog/record_imports/lib.rs b/tests/snapshots/dialog/record_imports/lib.rs index 362ee45..c6a7be4 100644 --- a/tests/snapshots/dialog/record_imports/lib.rs +++ b/tests/snapshots/dialog/record_imports/lib.rs @@ -613,6 +613,8 @@ thread_local! { const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBE : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBED : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -790,6 +792,12 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); @@ -822,6 +830,27 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { + wit_bindgen::rt::async_support::callback(event0, event1, event2) + }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index 6707bcb..d16eb6a 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -839,6 +839,8 @@ thread_local! { const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBE : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBED : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -1016,6 +1018,12 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); @@ -1048,6 +1056,27 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { + wit_bindgen::rt::async_support::callback(event0, event1, event2) + }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); diff --git a/tests/snapshots/p3-dialog/record_exports/lib.rs b/tests/snapshots/p3-dialog/record_exports/lib.rs index 0bbb4b7..79c7dfb 100644 --- a/tests/snapshots/p3-dialog/record_exports/lib.rs +++ b/tests/snapshots/p3-dialog/record_exports/lib.rs @@ -412,6 +412,7 @@ fn handle_label(end: &impl ToValue) -> String { wasm_wave::to_string(&end.to_value()).unwrap() } /// A stream the host writes: its writer, its reader, to hand out, and its label. +#[allow(dead_code)] fn host_stream( label_reader: bool, ) -> (wit_bindgen::StreamWriter, wit_bindgen::StreamReader, String) { @@ -424,6 +425,7 @@ fn host_stream( } /// A future the host writes, which it always does: its writer, its reader, to hand /// out, and its label. +#[allow(dead_code)] fn host_future( label_reader: bool, ) -> (wit_bindgen::FutureWriter, wit_bindgen::FutureReader, String) { diff --git a/tests/snapshots/p3-dialog/record_imports/lib.rs b/tests/snapshots/p3-dialog/record_imports/lib.rs index ba7da3a..cddd4a0 100644 --- a/tests/snapshots/p3-dialog/record_imports/lib.rs +++ b/tests/snapshots/p3-dialog/record_imports/lib.rs @@ -448,6 +448,7 @@ fn handle_label(end: &impl ToValue) -> String { wasm_wave::to_string(&end.to_value()).unwrap() } /// A stream the host writes: its writer, its reader, to hand out, and its label. +#[allow(dead_code)] fn host_stream( label_reader: bool, ) -> (wit_bindgen::StreamWriter, wit_bindgen::StreamReader, String) { @@ -460,6 +461,7 @@ fn host_stream( } /// A future the host writes, which it always does: its writer, its reader, to hand /// out, and its label. +#[allow(dead_code)] fn host_future( label_reader: bool, ) -> (wit_bindgen::FutureWriter, wit_bindgen::FutureReader, String) { @@ -583,6 +585,8 @@ thread_local! { const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBE : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBED : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -760,6 +764,12 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); @@ -792,6 +802,27 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { + wit_bindgen::rt::async_support::callback(event0, event1, event2) + }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index e5d2fcd..840e742 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -421,6 +421,7 @@ fn handle_label(end: &impl ToValue) -> String { wasm_wave::to_string(&end.to_value()).unwrap() } /// A stream the host writes: its writer, its reader, to hand out, and its label. +#[allow(dead_code)] fn host_stream( label_reader: bool, ) -> (wit_bindgen::StreamWriter, wit_bindgen::StreamReader, String) { @@ -433,6 +434,7 @@ fn host_stream( } /// A future the host writes, which it always does: its writer, its reader, to hand /// out, and its label. +#[allow(dead_code)] fn host_future( label_reader: bool, ) -> (wit_bindgen::FutureWriter, wit_bindgen::FutureReader, String) { diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 6f22bde..e2a8647 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -601,6 +601,7 @@ fn handle_label(end: &impl ToValue) -> String { wasm_wave::to_string(&end.to_value()).unwrap() } /// A stream the host writes: its writer, its reader, to hand out, and its label. +#[allow(dead_code)] fn host_stream( label_reader: bool, ) -> (wit_bindgen::StreamWriter, wit_bindgen::StreamReader, String) { @@ -613,6 +614,7 @@ fn host_stream( } /// A future the host writes, which it always does: its writer, its reader, to hand /// out, and its label. +#[allow(dead_code)] fn host_future( label_reader: bool, ) -> (wit_bindgen::FutureWriter, wit_bindgen::FutureReader, String) { @@ -892,6 +894,8 @@ thread_local! { const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBE : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBED : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -1069,6 +1073,12 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); @@ -1101,6 +1111,27 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { + wit_bindgen::rt::async_support::callback(event0, event1, event2) + }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); diff --git a/tests/snapshots/record/record_exports/lib.rs b/tests/snapshots/record/record_exports/lib.rs index 7195a44..a3ba16f 100644 --- a/tests/snapshots/record/record_exports/lib.rs +++ b/tests/snapshots/record/record_exports/lib.rs @@ -10,6 +10,7 @@ impl exports::test::guest::api::Guest for Stub { ) -> Vec { let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Host); let p = p.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c = c.to_proxy(); @@ -35,6 +36,7 @@ impl exports::test::guest::api::Guest for Stub { ..trace_return(Party::Guest, __id, Some(wave_res)) }, ); + SENDER.set(Party::Guest); res.to_proxy() } fn classify( @@ -42,6 +44,7 @@ impl exports::test::guest::api::Guest for Stub { ) -> ::core::option::Option { let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Host); let s = s.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&s)).unwrap()); let __at: Option = Some(proxy::conversion::executor::step()); @@ -65,6 +68,7 @@ impl exports::test::guest::api::Guest for Stub { ..trace_return(Party::Guest, __id, Some(wave_res)) }, ); + SENDER.set(Party::Guest); res.to_proxy() } fn begin( @@ -72,6 +76,7 @@ impl exports::test::guest::api::Guest for Stub { ) -> exports::test::guest::api::Session { let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Host); let c = c.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&c)).unwrap()); let __at: Option = Some(proxy::conversion::executor::step()); @@ -95,6 +100,7 @@ impl exports::test::guest::api::Guest for Stub { ..trace_return(Party::Guest, __id, Some(wave_res)) }, ); + SENDER.set(Party::Guest); res.to_proxy() } } @@ -102,6 +108,7 @@ impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Sessi fn new(name: String) -> Self { let __id = trace_task_id(Table::Exports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Host); let name = name.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); let __at: Option = Some(proxy::conversion::executor::step()); @@ -125,6 +132,7 @@ impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Sessi ..trace_return(Party::Guest, __id, Some(wave_res)) }, ); + SENDER.set(Party::Guest); res } fn name(&self) -> String { @@ -132,6 +140,7 @@ impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Sessi let params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; + SENDER.set(Party::Host); let __at: Option = Some(proxy::conversion::executor::step()); proxy::recorder::record::record( &Line { @@ -153,6 +162,7 @@ impl exports::test::guest::api::GuestSession for wrapped_test::guest::api::Sessi ..trace_return(Party::Guest, __id, Some(wave_res)) }, ); + SENDER.set(Party::Guest); res.to_proxy() } unsafe fn resource_from_raw_(handle: *mut Option) -> Option { @@ -1396,6 +1406,16 @@ async fn call_export(id: TaskId, call: impl std::future::Future) }) .await } +thread_local! { + static SENDER : std::cell::Cell < Party > = const { std::cell::Cell::new(Party::Host) + }; +} +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Exports; +#[allow(dead_code)] +fn trace_at() -> u32 { + proxy::conversion::executor::current_step() +} thread_local! { static PENDING_CALLS : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; } diff --git a/tests/snapshots/record/record_imports/lib.rs b/tests/snapshots/record/record_imports/lib.rs index 38540ba..3d9a1ae 100644 --- a/tests/snapshots/record/record_imports/lib.rs +++ b/tests/snapshots/record/record_imports/lib.rs @@ -57,6 +57,7 @@ impl exports::wrapped_test::host::types::Guest for Stub { fn make_point(x: i32, y: i32) -> exports::wrapped_test::host::types::Point { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let x = x.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&x)).unwrap()); let y = y.to_proxy(); @@ -82,6 +83,7 @@ impl exports::wrapped_test::host::types::Guest for Stub { ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res.to_proxy() } fn check( @@ -90,6 +92,7 @@ impl exports::wrapped_test::host::types::Guest for Stub { ) -> ::core::result::Result { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let p = p.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&p)).unwrap()); let c = c.to_proxy(); @@ -115,6 +118,7 @@ impl exports::wrapped_test::host::types::Guest for Stub { ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res.to_proxy() } fn open( @@ -122,6 +126,7 @@ impl exports::wrapped_test::host::types::Guest for Stub { ) -> ::core::result::Result { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let name = name.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&name)).unwrap()); let __at: Option = None; @@ -145,6 +150,7 @@ impl exports::wrapped_test::host::types::Guest for Stub { ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res.to_proxy() } } @@ -152,6 +158,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou fn new(start: u32) -> Self { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let start = start.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&start)).unwrap()); let __at: Option = None; @@ -175,6 +182,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res } fn get(&self) -> u32 { @@ -182,6 +190,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou let params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; + SENDER.set(Party::Guest); let __at: Option = None; proxy::recorder::record::record( &Line { @@ -203,6 +212,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res.to_proxy() } fn add(&self, n: u32) -> () { @@ -210,6 +220,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou let mut params: Vec = vec![ wasm_wave::to_string(& ToValue::to_value(& self)).unwrap() ]; + SENDER.set(Party::Guest); let n = n.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&n)).unwrap()); let __at: Option = None; @@ -239,6 +250,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou ) -> exports::wrapped_test::host::types::Counter { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let a = a.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&a)).unwrap()); let b = b.to_proxy(); @@ -264,6 +276,7 @@ impl exports::wrapped_test::host::types::GuestCounter for test::host::types::Cou ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res.to_proxy() } unsafe fn resource_from_raw_(handle: *mut Option) -> Option { @@ -281,6 +294,7 @@ impl exports::wrapped_test::log0_1_0::sink::Guest for Stub { fn write(msg: String) -> () { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let msg = msg.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); let __at: Option = None; @@ -309,6 +323,7 @@ impl exports::wrapped_test::log0_2_0::sink::Guest for Stub { fn write(msg: String) -> bool { let __id = trace_task_id(Table::Imports); let mut params: Vec = Vec::new(); + SENDER.set(Party::Guest); let msg = msg.to_proxy(); params.push(wasm_wave::to_string(&ToValue::to_value(&msg)).unwrap()); let __at: Option = None; @@ -332,6 +347,7 @@ impl exports::wrapped_test::log0_2_0::sink::Guest for Stub { ..trace_return(Party::Host, __id, Some(wave_res)) }, ); + SENDER.set(Party::Host); res.to_proxy() } } @@ -1465,6 +1481,16 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +thread_local! { + static SENDER : std::cell::Cell < Party > = const { std::cell::Cell::new(Party::Host) + }; +} +#[allow(dead_code)] +const TRACE_TABLE: Table = Table::Imports; +#[allow(dead_code)] +fn trace_at() -> u32 { + current_step() +} /// The host's result of an async call: at once, if it is ready on the first poll, so /// that the guest sees RETURNED, and otherwise from an executor job, which awaits the /// host's subtask, with the step of the run it completed in, as replay returns it in @@ -1503,6 +1529,8 @@ thread_local! { const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBE : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBED : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -1680,6 +1708,12 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); @@ -1712,6 +1746,27 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { + wit_bindgen::rt::async_support::callback(event0, event1, event2) + }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index d3d0f91..c903b68 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -766,6 +766,8 @@ thread_local! { const { std::cell::Cell::new(0) }; static WAKE_AGAIN : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; static HELD : std::cell::Cell < u32 > = const { std::cell::Cell::new(0) }; static WOKEN_WHILE_HELD : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBE : std::cell::Cell < bool > = + const { std::cell::Cell::new(false) }; static PROBED : std::cell::Cell < bool > = const { std::cell::Cell::new(false) }; } /// Counts a step, and returns it. @@ -943,6 +945,12 @@ fn wake_executor() { async fn run_executor() { let mut running: Vec = Vec::new(); std::future::poll_fn(|cx| { + if PROBE.replace(false) { + PROBED.set(true); + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + PROBED.set(false); if HELD.get() > 0 { WOKEN_WHILE_HELD.set(true); EXECUTOR.with_borrow_mut(|e| e.waker = Some(cx.waker().clone())); @@ -975,6 +983,27 @@ async fn run_executor() { }) .await } +/// The callback of the executor's task, which `run` exports, in place of wit-bindgen's (see +/// `patch_bindings` in the generator). Resumed after a yield, e.g. by a job waiting for a later +/// step, the executor first takes an event that came meanwhile, as a waiting executor woken by +/// that event does, e.g. in record mode: wit-bindgen would only take it after a poll, in the +/// next run. So its first poll is a probe, which only wakes it: wit-bindgen then takes a +/// pending event, if any, and polls it again. If there was none, wit-bindgen yields, and the +/// callback runs it at once instead. +#[allow(dead_code)] +unsafe fn executor_callback(event0: u32, event1: u32, event2: u32) -> u32 { + const EVENT_NONE: u32 = 0; + if event0 == EVENT_NONE { + PROBE.set(true); + } + let code = unsafe { + wit_bindgen::rt::async_support::callback(event0, event1, event2) + }; + if PROBED.replace(false) { + return unsafe { wit_bindgen::rt::async_support::callback(EVENT_NONE, 0, 0) }; + } + code +} #[allow(dead_code)] fn stop_executor() { EXECUTOR.with_borrow_mut(|e| e.stopped = true); From 5439c1c63924ec1a5a03f5efde743338f53fcde0 Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 17:52:46 -0800 Subject: [PATCH 22/25] Traces cut short by a trap now replay to the same trap. What changed: - Recording copies before the guest goes on, in fuzz and in record mode's pumps: - Every stream copy now waits until the other side is ready: a zero-length write until it reads, or a zero-length read until it writes, which leaves the other side's copy pending. The copy then completes during the host's own poll, and its line is recorded before the guest can continue or trap. - A stream chunk the guest takes in parts is now one line per part. - A host-written future's Value is recorded as it's written. If the guest drops its end instead, a guest Drop follows. So a Value line now means "written", not "taken". - Replay: - Its jobs do the same readiness waits, so the same events wake the executor in both. - A host future whose write fails now checks the guest's Drop, instead of reporting a divergence. - Cut traces: - A new recorder function, trace-cut, says whether some export call has no answer. - In such a trace, check accepts a guest's line about a stream or future that the trace lacks. These are lines the host can only record after the guest went on: its drop of a stream or future, or the value of a future it wrote. - The jobs also take the guest's further writes without checking them, so the run goes on to the same trap. - There's a new unit test for this. --- Makefile | 6 +- assets/recorder.one_byte_page.wasm | Bin 380006 -> 383789 bytes assets/recorder.wasm | Bin 380010 -> 383790 bytes assets/recorder.wit | 11 +- components/recorder/src/lib.rs | 3 + crates/trace/src/lib.rs | 51 +++++++++ docs/p3-record-replay.md | 30 ++++-- src/run.rs | 3 + src/traits/fuzz.rs | 71 ++++++++----- src/traits/proxy.rs | 100 ++++++++++-------- src/traits/replay.rs | 87 +++++++++++---- tests/snapshots/p3-fuzz/record_exports/lib.rs | 76 +++++++------ tests/snapshots/p3-fuzz/record_imports/lib.rs | 76 +++++++------ 13 files changed, 348 insertions(+), 166 deletions(-) diff --git a/Makefile b/Makefile index fa53fb9..8ad63c5 100644 --- a/Makefile +++ b/Makefile @@ -106,14 +106,16 @@ test-record-p3: RACE = tests/fixtures/spike-p3-race RACE_OUT = target/fixture-race RACE_WASM = $(RACE_OUT)/wasm32-wasip2/release/race_guest.wasm -# Fuzzes the main components of tests/fixtures/p2/guest, the p3 fixture, wasi_http_p3 and -# the spike's guest, and replays each trace it writes, which ends as the fuzzing run did: to the +# Fuzzes the main components of tests/fixtures/p2/guest, the p3 and p3-record fixtures, +# wasi_http_p3 and the spike's guest, and replays each trace it writes, which ends as the fuzzing run did: to the # end, or in the main component's trap, e.g. a guest's panic on a fuzzed error. test-fuzz-replay: cargo build --manifest-path $(FIXTURE)/guest/Cargo.toml --target wasm32-wasip2 --release --target-dir $(FIXTURE_OUT) $(MAKE) run-fuzz-replay WASM=$(FIXTURE_WASM)/proxy_guest.wasm OUT=$(FIXTURE_OUT) cargo build --manifest-path $(P3_GUEST)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_GUEST_OUT) $(MAKE) run-fuzz-replay WASM=$(P3_GUEST_WASM) OUT=$(P3_GUEST_OUT) WASMTIME_FLAGS="$(WASMTIME_FLAGS) -W component-model-error-context=y" + cargo build --manifest-path $(P3_RECORD)/Cargo.toml --target wasm32-wasip2 --release --target-dir $(P3_RECORD_OUT) + $(MAKE) run-fuzz-replay WASM=$(P3_RECORD_WASM) OUT=$(P3_RECORD_OUT) $(MAKE) run-fuzz-replay WASM=tests/wasi_http_p3.wasm OUT=target/fixture-http-p3 cargo build --manifest-path $(RACE)/Cargo.toml -p race-guest --target wasm32-wasip2 --release --target-dir $(RACE_OUT) $(MAKE) run-fuzz-replay WASM=$(RACE_WASM) OUT=$(RACE_OUT) diff --git a/assets/recorder.one_byte_page.wasm b/assets/recorder.one_byte_page.wasm index fac317072ff91aa3a6cf72554ccbc700bc340ace..cb7b2cddd0ea55dca7def2ba85d9af7f4ba0f8b7 100644 GIT binary patch delta 56673 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�>W>mSR-8 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 delta 54933 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�HXbL6zdZe=0#}nfA-W6c>%7UIL`)~$n8>BEP4 zwdOVtKuVFSfnVwdHg~j}%S_(fhPDAYeVcjcDm~LyTeis>H@DSPZ}pqo(&#PC)-cmn z4{y*V>fizSpNa>xfvR|fX4>3%gpm?qBLyP?kMMM;WH1xcxu1Y`X4)_=w@X>-&>QSZ zSD=Bp87&|!-_K`^g0Tsnt)3gsIIV$aa zRS+@}{keQ#=fxK-Rd-0);qoOv11U+Bp352<6>f`WQIRfZM0m+%&Pe%(=&A%#!RQn4 z2$czO=o#vh4xEC2JBN9gYS4QUq{ zS{zib?9PpHKqzod)MkgQ3`j-?K1ecRWN)ZUW##mAm1_Fd|5a{yL9b$qGAqFkOMM^Mc8?C!T z{OT2YhYS6=VAi`MNjPXINZ(}GM!MNRpX+Iqz?xN$D?q{} z5xN<&Fp_NWabMg8nOTXWR{B!x!8#Pe`A)VCLJK= zKO3^7HU=fr;!zOCx+|E5{IDLe07@0qsNgNvR8fQAXu3&~9tLRx6I5w=U@B@QI=|Hz zvm7RcPy<6%0jPiAwoTokgI^B5KNn?;-33vdwszz?ds?gU+ zWMCmdl$n-)C^=Uaf_YooCa!q6b`yHx(p3uu#}X|`NTn>5R$@^?`_6|I?; ze<-}hssW`jt={cT7!9gMyixH8ilD+qG5=3 zs88+?izPtnx;dpWh!~SikP%qOj45p^_$?JZ)E!z=%LRK+RZHH>i*_<3{M=&pyoADj zIG#rpDeUChvIozU_K@NPD#9;0XY5jUSI!xGz&XRMGSbyz4|2}f zrE^BM5YYIMU&y=?cX-_vW+G5({Ia|?NoW;Dp~)&uGYI?vQ0U*tDZ(~UV2a#ms{^nr zwSK$XUe7kPIctkS1I&M9+U`(|nQDKC`K9!J&~ut?2sC`VKb4bb2cH9=;-kg>Q1S8T z>j4&1c^{}bc--d!GSh55p|jzuI~E!qfsi*}BlUv1@{N zYwgfHgZ)_=okrlwd}uThS*7fQECPkhgAzJ7*)`Z)Bh!EtnFcJHE7L$am(VZmxs-sx zUra75WvZmADLq}%lF*dNq(N_03>^al|HaHe+lU-SJ3;`QxoxD7VH#g&|YilnAyYG$NZ+;E`y)NG~VxNb09dPe8UMRW-|$Fz3~)NndbrQZY>l zxNMaI3CqS!Fq<=s8JC_*nFNoOnq-Knuws);o-4KJ&=8pt0jog?Z)HkET6Id85?JfE z^vy|f0X(K&R0DTeG0!BmtZ~qoKejqFuJ0&$9i5!|6 zAjh;K{sTaQjEH{&3N)BoO%#@B46W;Mtn1=qh7XAtBbPv7`9!!KiUu4+wMC{cnwBD3 zCb=CZ4PcL=NCEUKpClFut#nTOrK++b0>cD?9RD8oK}N*C8FH}^`%7T||6s_)M(j6- zEUk#YIOHHJ;;#(3*ogiAC$n5^$mWQp5#jP%Gy7+=EU8VSFb(PH0ADO=MUW8@SSm&3 z$}}R1mFq`ZKGtB66`?`WoCsJEnq@^u_Qv$@$Y_X`Tlf%kTtBD2V{Bl4_;zdhd6i<8 zzi6Dhi+1x%#VH6+3ikv;6U4s{RLJhRh+WFBbx&R9IGZOU3Jw^3dsR)jW`2wTVAwB^ zu|(#29k)`JTV*WhmCP;~J9@o{-`nr)>o{UHu;kt)Y>jd6eNFV$Qo#)K#X-@6!Lr(z zecw=4XhijH!f%c?+VyVfR+|Y}?_t>YRN)7BgMw&E9^>uajo9k^PkYC5uiAiS9`I90 zgj{OdtHXj19S{U5~`-tAwVtv7b}9}xY%#Jem5a@t_Dd8`}qr#+U2-%XD-WDATR z9%~mjmu6SB1dM1tH}*vHJ3l^>#hCsTGmq;Vjjx{AMN_tLKx=ciGQ{alL_X9feU|at z0AMG-+LHqrn`2DN>L)u(G$IE^;jPlZX6%O1YhXI^CJm%L#?=Gs;`f_@X%Qwex&FRU zY0yZFWX_->+(R>zxIZGVPO*Jr6E zr{0aof8Jq|B3$TFY0EVtp6zTb`)3%Sf4|J!yO8tXv)OF6aqQWS_)UH8HvA5Ft{>7r zdaeWB%RQfo_ioQW1#;Q&d|gDSoO`}`l}$8i$WhGdRCo|98lrtF1`25V!prP!W6KNo z0qUd|X&T$UI0B7reX%)yZ@$hjP7F>Q&ZQ+{xj+3Xc|6T)tXbc zI1R<@fzue1UZpC_U%d@gF21^ys>~kO6;$;7I4_bSUt6b=8XCX9Mxj;p$5U`rxADoY z3$VwqH{`3DrT=O?KfXajlj5-fk>z`tOd8tZx2!kff~hjtk>2Xa-mx)1zFHkc_|y&> zi-XAm+Ly~W%-0w}4@fLT{D$mjzL7QoLcq9lLQO=SK0m>u2n;Hv^21Br9>yv4n!jp7 zl!h>_uJNwG0J7psKDu4Zy-7IE}oDMRR)Vv(Y7t^j$MATFxB}rllteXcI zEOa%=f1T zeoXOq=mq~IH34$a{-;_T;=Ohc7S#m$-8q?nFicXKeX?9w{SURh9 zf{Ae0m*k4m2!n83Ko;3&-_B+j9cFtfT1Bw+T@`(kEQbCWrsw33n!UfM8`$o$o4#|p zp_>(R0^Phc_a5NzA~mx5y4P?zpzHd|5et_VLOb<^=)V}l*Vm6Yy-I%S`gvpd`W6i8;ri#<_r_xz z+`(B>Hq;9K+`6G=#4^Wx*{HX%JSh8)jkPN;Cr0LSK>krY zHh>SKHw*a58=GTRKiXJg`c527GNL~ycc+@uQ0(xVN@QRFSE*U}SY7s_%m7rsSebBa zz_AMkNA#qBZVde(1z^qmpf)<)`~ii}fAzti01$%USQFTUK~mrh+cbxLVZ;L-oiA7)16 zD-1h;;WZ!jE59cgyHTpFe%h$FBi^fU98N1yo`kUIYJJfhJ-*}7^2O$-sD~bn*0=2L7d~e(}rOf$Gir7Qa2^%Ds>nr=q88bhMO;roiB^Rch7N&d) zb}b^%AOcjd|D&Y-Y9U5geFmmP5n~i0!A)oaQ(@)Ut(*Xg%^d8RI284$sr=IDw)-v+ z!2I3SJ&Nhl6lM8F+zBbD<2|74SfluTcRXk*W)IO-+MY7$>cl~^7`E)0%Z?h4?41#& z=HG5?E{rcpS2)x~b3p14(N(>D=@DDiNCDp8|J#YhqlB!wwNhZ)%in?Ot*^zaf3<-|d^vEe{e9g8!XXfH7# z=CV{{IcofTph@xG${D3_S7u*?yy%bTLbqG-@q#7_d7-p;!h)T!I~AHjb;)-<;jVB( zSR&MjlRz*sVyP*c_4m+QYFx^`65Ps{^bK5PA16lj8n)`G!2f`iuhb1mt(mpmJ-~Q7Xb+#80A%C^Vjh} z!e?K%jVRV`SrU8EsS=FHl-L$u!Fo4td^?6MGp3#%UU?ZwH3|V3;$`rig;Hcf2Rz6c z*kyRWsT@;SLS-Cp_|87bb{TJ-^BL#Qy4V5Z*Ru`Tdq$n_>f-l-?`|)%MqE`iT0cww zh|r?q*us?YYsx!VKUnuyzn%DB1MJ{`6RWzGRMg+;fv*UZ&4kA0-jiMbjIpAVp=SRLr+w3%UUO6`9Ihh z@q(SVK^qs{ybd1U+Y!=0huB1wx~yx!T1TJ&!6aU&%ivWJv+A;D?1DI4myusZI9sv$ zBEKH1g$9n*WA|2?L#8C@Ri>USkEkKafcUfUh4FTOanNU(nCj%A|9d01mJ*JN{LM-E z>VI)kwwL4GZZ#FvTe5Ua#e*#aQ_(3|dYOvQPfA0v8JHITw`bsgbC{nb5m*ANp?pET zO8o1hC4sG?z>;ubNjT{A8g|P;(~YX|-`QMIFPV2MS`Z>RXh9UOj4#@H;I#EnVnJlM z<$_q^4qgyR9{wP-!RI}kTo^Mw96Cg4eNFuC;m{&PEibQQIzRGfExen#lvgqbVVLlB z{kG^(v4s3D7QbgVQ+CQC`4d(&w)-uAGT5rm(znLFueNY#W!Z2r+LxmR{LU0*Ty{C7 zqI$!86PFcc!~4(#I?@s4C|D3^X=iXt%;)T4Dz?SD3^cV_HI<5{#^p<(tfpvV!ev%I zt9n^wj9=BV*bt3RR`pI@xLkr$A8q8QfthWXwHK;}4;ek*A6DK(hnclgHGIf8`hHF0 zN{jZ;-TudWYT|4Pf114~?ybseuqDD@mB+_jR>QnRgF2$X$rTH$^6h+Lt{7a6m*+Eb z#jDl$?TvPjzym=d;?06lm0zb&3_l__Gp?B#;zl)|!M2Mg)%jK8pml2S;rz&Q@pcWs zu|ve9@^XHq5|fSZ4j{F7yP)L~?V26B0yTCPub~Bed(I(@`*~}y2E?p8D2N2UX4<7! zj&wI~h834jBcmfl%0q0nZlfyjmgyfz(GNdOIv?%^p3?acJh;fK$tyUV(;3G_10%$; zQ!F_V{h%7^d`NX*RchwB!AYWVEuL9r+$u>)xn>u%u~ii%VK|R3&tt{JTD-bgSBt0I zYxjkaQEDOdh$JLP7A}wQBN~QIq=&e9nhhaWKAQ*D5^P;^O(HTACtUCtf(SS19VLLE zrFg^i?Sj|ljVt2FXjKF*AoQlY7x7?j_1?wn5_i_-71&nsL~ZWzBlwL#2zN))Ng64k zr8SLob2`=(!#Y8rA|}b!vP={@29MK-2;l;50M!5$7|7{}WjH1D_t=pWibJ47y-_}n zs3ClYMcVvo3}N&b?ukS*9Dz-dZiLi@Q3*GfU}2ZSS4Y0Vhi9h~Vwf_V6GD_xI32D; z$UiJ9>Kvv%8b>>kvK?yH%kPZDITP8FIm|$NVoe=h#lM**hn(b*2=6ND0ekLrq9eEm z1#VOjxL{WplqZ^|#{xYRy}{dKf$k2=!hsJ7zYm9CiNiT42+9Wn$$qY5q@Pj}sI-iy z!bhBpk#efCy8Lj#qocNna5yC}c<`M1m~#|F0J5YDihwMQ%n(2nj7-@-fI@IxgzsJ` zadhwo-}cS`&zgCY9wP$uh*A?_}luD3pqDO{7;vxF){bo>;7mYw#X~n_E@LtK=;QuehA8aCbT1;=_v%BRUR* z1N=mTZtNZ>tuK^J@Wh$haCVSzH0`Dmm3W=)B5@8F#DWP&6LQj{-Qn^$k6MUv?r>{! z%!yZTgj|Dk@(OP?d4~*yzu}Aw#)8?yc1jqyC>eZ2Q!F> zNDHN!@2E_uRFpu=$p0K!hJY{Ab|1Wmf2H~>MEyavLC}Ap^RcS7KhQaZ+^ws>TB0xP z@hhSU9$`&{A;%NZbnw8dghN58Z#6SQGG@%3ngfR{cR3Two!~x9%BO-16raVwL1dI(niB0(ZI-XZW!N-yRQOYk-W?4P=2BI|KEE zcPi~g7MIxF92LdQ#yqrjpk^03ERREt0TlqqLez%RbD%#$lG3q4OSQ37H*rT3 zzB_SZZXp!-nwZB3{NT1Dvv(+=wbOZ5N1$|eI$TiKOen-IT=2nO6N$KZBH=r!(Ym83 zmYDm9JbA|?2uSfm27fi-T(MkeP`(hAJFO|7*7*<(AMz68#+e3i6T9zH;Do}SwNiOI zMRTqqw8>wlT-H5EHdgFv#@jh^l%|v`%4foW%M*`f^4n^w?Gak% z0M(1w2a0r3(TAAZoL6wl z_6e@bHlx>h+CQFrK2Qx?z(v&%wL~A&FI0!s_n&TKqrD>+9#3CN@qfev=t%R7)r2Cv z%$6_uILf4R*J@p1-O!;GhTULxxh_0#^)HhL?$e8n@EdWddHi{0Fi||wQfV;xxPI8U z|I*O^NjC>eb@N+MHyf;OJ~hrD^)&t?E!v>OUAK zeyk9`#To_k?NOr)*FQ5{KQ&+<8?AoIVuy`IKXqnDjH^Fo7@0rE8m+E2#n-b}Gw{0t zzre`VyYbun=eyY_#uWU1mVfH!rx*s<<647=920tdg)#A(hh-bfuho)S$;N{>s^P85 zudVz+n8LxUwI(vdgONE~wS$BASU~!^vWmPLgs8Ie7Y6bP*LhYE-rD%`*QAIElBa+j z&71yPe9fXSDn?-g%;YgUR4ZX>R(~>D{no%7>OPed8W`%{-%1U&VbD+!31AHss&x_F zELH7*u83|{8q0pKRvbEi3Fx5hSkN6ZVKAYi^$tTesK6JhKzQ*2Pb_{H(a2UAOK$#2_rKok z1&?u$S6K4Q!MLkpg~0ugDYzSL181P?nIFNMeMTf}td%R!v!<^RJs69N4p0HHa(%s+ zsKIfm8WSHeRs-)5c9tP(azqLJN1ZtGOw@|5j3q=Dm@@$;i@~lcqk{r;Fo#71*8DZG zGK8IqGGpR=*)mj2yc5cL-E~zpMiD`Evh)IT;)shRSiJ6+KoLE^to&asc<9bU{e96n zOb&Qd7#rHwluGirgXzQZLN=)NSX<5yhOyKV8XJ_*I0CI5_MVRcRCq5l9IScP$EuD? z-%6RN3FK~~S9R=09zwFnc!w(METb%$$APe~(l^ts=S&lu`?7N4l7l5VcjwV^fB@Z* zC*s2~_pgfP;q39K{Gg6gUKK0CS$CHIdIW3CrivyJtP{%@*%7P(9w%4uc(EgbMLTG- z!RDOE#O0MdPW%wT>alUc>tu~ks+W^Z%G@YBg9pl>|Py>P5+<7Evq}Kpc$%ER)2D zC}1OBm&k_8u~GsdHl!J#763SB{D0*hA>(8 zIg30><@{n1L#dom3Zb#3TbrSBTqSa9WZQi6Zdqxi%5g&TTc=}o|I6Bz3Y$XE;3AEM zh|RNDoVYudHDTk$Yq4y01z~Do+d!|FY0wA!B>HI4q8w`ooo;YBmSk?;LOJJjP#QYw zVlf-(Me&EIqXl+f%Zmo_YWTO~8=Q)XjiPA<pRB(_l~^POJgE{} z!S;!kmD%e7xX!EToF_Yem*6^I8KO=Iy$Y+)*aAb6Onf>&Pa<_8s)XuR&t?M)Wdlp8 zfd{IvZeVSTtFVW`FC&s!r9f{c7N*L=GpMj6pL>#7xsn7ngfKB2CH%>(B?x$9a&Y`D zSwv@&F^zd5(ajc=tb89;?t{ulz#vLycBjmq$c*){H#%73FhLtYnS)IH)5Sm-Fs={V zNdrHDrByU=vqE30&>yY+=wXfH#}R9Wiv*Msf*>V0xI*+-MGG&hToQ^7RIdx_0RwMx zYoba35Xmx-;^C=Y_iy$$(qvu>p;lN5%rrxCgq9TI;<@cCpVXg zRnlbQf#$4{3dDzRFtDc0=0#EqHjp357MIdkEiAgkx~#{ocBubH!2&(%gTgM4DHz#` zW&S6FJi@|U7;=>UUmoh0YN#RdlvTlv8m#Gm0u4|;{lAOyqXCrXw_vy53gv$zD4<|? zE7tx$8B+km|K*V#vqn}huPtj)1POCP;SGo$GV8FblOuk+gMEX2{}b)Ypy!7=ltItm zbSR6S&DKTzj-cn`5`^j@s)wB&SyhB1Tya?;Ue#UD2ql;uQ@m-<2=7{!P z+2crD-c=@U@5;Ot@<dhOut(en zBo!X3UINu4#RT>CvDaR|3o2lasDGCnNc+3Mt5td2&1d$6H^r-8yNh+i;unYRVt3Fk zd8NDA?TB^hdp9ejPj62)9r${`Crgbxpp;D9Z*KGLQPxtfxY83yTQ2Uqr=*C4)3Ab( z)gmTT9W4;K_ppY^^4OQ$bA@3+C)hbLa@zVP-RFj2z1UuKTX3Kk?h6Ok{Qh2~ToAtd z7#*_|uHI|_QlII~JnVNNdb0=knUP}fPTok=xSyq=M? zvIh$0J%GQ1fx7SYkuAz{zf-vb)E|npqTJdKvxz1J+OgLZ0>@;oaQ9<(vDKntUtEGd zN%ZZD2699}Uv~eWmYimljCq7r^Zttj7;jDgh;$QZ8v~9vvZ8S5jqK*WN7x(eU*d;H zU~`-iH6CR{@%6@|>`wId-J@&=J6e#}pLJ#g?;nrB4Zx0y=1;Jmj-zTWRtfP0`-*>` zE&e%x^`hs#0c;9AAAXWOYCg-0FP>!G=%ZE^+ZnEo;c5DH@p~4ea-K*Y2>i_zEeEpX zfX!YAhYZk=HIO|B4DTGsb`n;n4`Sz0!T%JifC}O%1}~&I@D!}k9N~GI_2%bSi2m~?OpctQFQ){cSdUK$EI9$&C@D6HSg z;|W)4lP3#XEj~UJ)*xxgkt}7rxHyb`$d-xs{{gO)Cw}_}2xO_KG+dsEYC0VJYq^NY zFVT;fGn~~m??;D3#{oE|FDv+RIJ-`yaO9utLj4&9444sDpOt8=_#DGUy~6h#GyW@3iQk`x%5g@te1YAI$IKU42Rx3w06a|)RbP}& zj|X04J)vpmz6iq%C0~L@bmK*Kn|i+sv^4c4mZ?Y%yvj8Q_cYVAe(@5U7hmi^2>2@0 zC;haT@-pj-QCxnRQQTGX2r#TIqV)(iJls48y(L7994SG8zp8#1OkzZz1Uy##{E{Z{ zw@p&SMn7icjKF(2@q-^m-5HUR&Dy}d^>{YBA77iZ(a8#NIU5?cESD_0j)W1OEwV;J znU~J0^dfIq%xlnzWUXW|m>gs)gfWU`BB{Y-mMkueV#%l)Il7FhJ4ah!wPUM9`!SF} zXT+c}?4h8_4)KFZz_@~XUHXbuwW<@hbz!3h+m0CU{SyE|wiq$N>aMzWP-j3)2rix z3K!3 zj8lUZYQdYlh(sg_M_t{phc{dmY#tUR#Do9pC$hy-0awCB(I*(|od*$x^a$v^W*7t})X zs_i30DODJHBKPeNY#^UEkL`)~XJgTv7GKR~!1|T!on-H@5Lo{^oL+Vhx+V@K$B%5NBho)Alo5cp3f!}M^=E2D!uAYgiPxX( zvtQi3l)cDa7yFk&Dcm3umO-amE*@MVh1R3XSWwsrxVw$+HKHY;!cOGLl>qyKFx$I~ zQP|0^%OLA#ih9dgYcxG%Im4|1!dTAU3E4mc5LK43WGz$=^z-}*D80+Y+7%^*Bii#u z(C)u!kw)j@PH~f-G}^mgE*{Bc9pQ*rn#=ADc>B!zWe74{F@bf11h@b^d8|UfSz(qD z?epMX$Pq*G5S4vJT+V}{dsTQ=vPKbsh>~k!+A8?y^ThC#7|?R@+G>fNgDWM}rHz#6 z0_GJD=Cg2Ac_N>!VK;?)70cjbR|wB4R#6N?YY~A!l0fSVRt2|ifB(-~pZdOR{mu8; zw78#XoV+#dGGMCRWR~ZN+g1ZD0Z`vtee1?(!kQPboiTe%`UAmWf`{p!yuOabJ2se6 zOrpUWn2o1}X9KGk`%?+(9w*O&iHA3^VKE<-Xyza_YH`gRzD!YnfO=1*h6qAXUiuTY^^!ZEft{BCWmOwy@fw?{*d@ z9Gh4d%xcme*j2+fK@I&|O_kfs?zUPPyZP_6vcPKK?}LF61;F@Z``-hjwivd9^|N64 z`>jmhRn$tkkNz$!7*V9yw3|Jk0GeDDanZ#b;`|=ws;HE^qMI)R_cVx@y$B#Ei@5gh zWu8B&7ru}6^jPjiaP{I|S*%SJ6gXlZON&(DdhqOGk&Y8<_JOhgm5@I%cR%(7t`~@* z(|8S>s7(fftjh)XJm4koG&%o8;KJ0);Q{{KZcx9r9Le>~ojfQYD1 z!LD*dk5AzO&JoK6uOq~#tO`FnQd|)nw?qk74)&>6ifK8BGnFn>1ee2=_4-iWv>@*@ zR-FgrGKI3x1b_QEJX!GYf5D!2ELD>@UhMpWHHx>sq^|^jwwW{=O`Q0GB{di$<&qgj zDz_*}SxL4Of3xN(5(yqVbb`~O&zI~m6C$}oDE55Gnn%i$y3{VhhDM3Ejw1dgN7OsY z>J}?H;3%A;f$TL$*`fv)scF=MiyWS8TwOvt2Ug-u+@pD?%9Lgv#(A+|`d93*MwptH zu2C=3hVmL>$w}<>lt`=nb?LMvWu(p!fA|ovT%F@#o?Uj@C`ddPV5HXvR}+w0I^PS zcvIwE!fx(P(cvtd&^yHAXW2RE!v`+2%z}yEVS|U|6-55Px-#+YImo*U1%I4l!#N5r zyGrE?rd@(TnCrG+LdjjpHiECHcesTi= z*oFna|I7xVK*4}(@I9cFFMnkf2!J1dXPwmHaPXOM8j92wolv20kvau&;m^%on(D5LL?Jr?Y0jz4UbIdPrzJX_)OG42q>ZhY63vSDRJ1Tv1A z8KR&iui`jweSOuEcdV3M#hYlbB;%qzKIq=8q z)QDQ&!Q%&PF#9pR`CuC4^)vB0yLe_O*ge*snYn7coQ(sjW+(iOi!$&>XE;@F8}Hy# z{4(YXM-C{Q72M$m;xPC${T{wcYM2SiBN&wN>gqz0H%X;3-c}ijr7||CjEcoF@b`^u zzQZcV3?>WeeQWQ&1-g!^Dy2tq zNo7B2*sv4t&abZ%V>ZgaiC4yM z<*rV=FEVR&mVcqSeP>=J;~mu;PU6Ff1?rq3X&W!^Dk3){U`zbbm|W!4_jTjB~EI*bRU2Z?5nS=NaPIo%{hdMPzp8 zZzN8YSmC%h3??TjnA6OJ2;u3$%kl0kgUVJJZz3%1D;jy0{Q$>gSaOc5X@$7y4E(p+% z?&Dr0UQmfBQeHId&F?|dxZZpy2K8HSUO(mC4U$={GT9s=4gyTh$`W1g=XaDfH1V(d zd8fEC#yrZ1IZ;k5X`;CbQXb$nxMPOdOTk?a@~aG*^{L`*-P zD!%Q@YpM5MaO4c=$L~c%Z(%=N^DtLj@5kSBoK}>#N-TN=M~vo*FCXD|W4QGm<#o~x z!awO{xGCG@#@keJO9ay9mX-CB)zY*_adQ8V^|kg<{wwUG{QkU7oD?HwOXn51hs2Nl z`9T!e{uuYve$(m^`j(ycBA8OY5T+3Hr};>mno_PORxnxc}3nCefr~>{kgd5F%j<-lq4U=N2nntvzWJ==l zb3CI|ZlL4ap66fR3Md(E)a3;}1d?Lo3!tJI!t5A1k2@_WX-l`?i~RX=5`NNb2yx2x z5*Jg*a?UpM@?;9(>5T;HZ~pkal!2Fn8apygeTC)Z?sr)E~#oBOrP3tGuchIF8fth3CfcZp|!-i1X2wL@YeMne;w$ zix@{sXib@YeH6$OH^-sVy&~x~p43dLm?#-^j_jf;=}=k(@(>4Xweqvd(;!m^qdHod z5Zp@8VXW6qNLZo+tzfs!Y9&YI;UI)Kj_L^(5LgFHtwJnC^eFkXp zldw=mzFJ5xa{E9cA=1WM(^Vq4y*bdu*QyJSM(h)K{iJVcs+~dG>G`Svvg>fjRXH4$mrdmL>#NQBQhcODms?dW8d&5c z2<*M$kx3A}`C`o^-Vk5sCP7l?i^R$NNj%0(=56uVHJMj-Oq65S7l@0KdDVCm6QrTY z@jJt9`uC#76yD0b!Y5na!z6}I!KFw!V*eDLK9#?ssMVKUNYW`+ z?3~KGl&aNq8ZN>)B8E%@Yk5zMoyOl_yM%i>-|bkqNvaBvf75xBM&mZiFVnrH?6 z{i$1=^_hH962%pg2v$+?fWiA9uz!ugds@4V*t3TjhUoYi5axNJ%M3ob-Zj;<`G@PO zV;w2VMzf>O%4Ts1kj-~ui-JIe&%`)(i(WJNz5H6CSTU1NcbrvitP&kx=l5BinMaAm zir2ARaqV}DK30wehRP8S29A6Y>_aj)<4v;+Yk`R=5VQ zwdr4|_k|4S!MZoWfvvCK;*BbukewDSFt07m=Agg99IhV~4d!r9rR#rI;^{d&g}o!D z&*3%sZ==P=IXt1#I9gWbwM3x-zbMuwQPPz;yp4IcqLgrIi^b$eQ$?4#JW*v+IK6eO zEgD;iAnY9BpUc}ppWi;0w~RSU<8gRJG9e7nGaFqgYXTeM)bv?-vc{^9RdF zo;RQ0&b}3==krwP3z2X0I;~5!NqtbDE|`dZMj_Coun^zD(~`m}xkG$8g_6KN6VJcR zlk}Rcxucylum5fJo2$$JhTYfZ0Y2wi@4XEvf$EN7ja*WGP@*4XyD>0@V=X)MD4{` zyQf8$#jugS5)&4~R{csWT+C~?SN&;eHvEMG1O_Rf$rzyhS~o}yM@m4gWfu$Va*Pp) zOL!-2Pd~PVzwB73NMN`4ehJTynf)=L6&jk4bw)%k#=m?iuZm#4PnL3W88=?WYxc}h zoaYoF1?xl+7OuX4MocmdJK;Z3mX{o&7+zseaX?CcH5sXgWKZfY0`AKZdCPb^er=Sv zwv5+-53$;Euz>)^%v}^UNneXjn;;&J{2dBd!EaAAXq|f)$Pf6!9}!NHAxGde6X=X+ zLV1(g4e>7^C;+@k)-%gb|ywo~Q@sugJ;6ivLV3$m7lV@1w-&Jf3B(CH%!8DK7e~ga(-> zzFY}Y_kyUKkCg~zzH>frjTqO7`TU+xr8J2tedPZ6jeI_^*%ktqw(`8 zDgd5yL{0&}%`s20rB&j10iO<(3|qr%n{moku;65|bPZO^9&v6BPpv35R&W*%r$tQv z@FB-gXDx{NfatN7H>!F_0zf+n`1@Vbw&nT(`PW_KO-~FHI!-KHD^0YWYk9RaWfsz@ zE5b;T!!;6{8~8Fe%IK&xK1AX=ejopFlo-Aag$u;$b$ls(_FvB@V+=p8=bfF`sW%&G zJo^heZh(c8GEXiF#B*DhtspK-rO_V+CVn|vyuJ~4M&^i!51>+w5qEyTdwETk3obyU zBMnk8chs;h>&p>4KHx2Bd4+C*$`n}hav^2^<%QG~BK#kPyfbXEO<=6pqc~Q`>*I)F z^k)9Fh$> z0ILpktg!^xxouh%5xb37gVoh^8*f;B8!ZJ)6u8kwwczn#31&B3YP7sR6jQhHOaQ)n z8&3?A3V^;^oZrT474!Fdw)5_e1C}!O{B~Zaa<2RW1cfTubQ6LSC>mzVcK&vgA_%|~ zzc?>phO{B3h{MaO+6D_W;zf4->L~HXhdjB@)U8qy2Xe`{l?5gc$+#IB_*ykccUmA5 z4F**)M=Tkf{3{%w*rFrnaXWY|w{>=(8U~KgfZ68<1d+Z;JhTHVaGMyvgIB5*3=Y4bvHRO{WiLE0Uz%J~AE=AJz;5%!5mdw4zS;DbF}(DUBCFyVHK zFZRmrTkHcZ2dcchj}Nn;#(4tC!*A^48(~{Wj&^0g^PN={EFUIzV*+j2tn$qnQPRrc<$C zCel;m4yh^DAB33NE519(Q(~137UUATEUJ7Alhbqp;1XEuC=p1w>6^B99Pslz`7wMX zm&L0e^M@Qem1(w1T=-a8)Zw4-w&gET$0a>e--<^+;SFy;NqNe7qMr(SKWo(it&_uu z{8`HUb~c5%WZA}E?#mTNK7lg7S6u%D`{0Mg$V2=o_NlmZh!?txGY}vYAyKdrK(-)x zuD>nzALgAC=E+4xYP(!y^yBa{y+Abllvn51Hj4W{h3K0nrhLj9dJ70JE=8=aB&-z* zXw8*4^(k)xaZ|e&uU-Nrx@@h_v3N)OFmFN z>WJ*4Jfq?_L5Mvc^!}YVaFjoUqS7m9F3_IGcy;gopi<|8-oK=G=L*I->E-3cqsMs# zvFjL5sIXI+ThKh9_|c5v#}3{6+A-b&)Y0TPfAE%N79Hmg1W1flK`@EAKw`-!FvFL{ z?I$24zg_V32~L9^e3Ex^nZk>a|Da&|Ni0k-h8m}en2W_hW}V`-EUQ7576(t^YV+x$ z@N^NoF(s&I^bJq51eYp2=o|iR9fJtPgM?r~60}%Ml~mOj@hwk`H?bm9BDXL!dVW-{pvubL*;94Q+nI`C(z9l}5lheUXX<=SO%OW@xDZdv{>k{D+hO&6RKV%H!I?{pT!Q;a(UG(+PKu|` zL*1Av=AP#rpbeir4`0S);k*FX*JM%c0v`}{Sk^UHPL%<7og#c6Ok98%#0Fg7kMlG<7n~JMLv>E6PGUXU+MGvOZ;6N zqM34;-^tF3e&p5;{+L;~shY05{r&%~Z9ykoo1>C@-O%)wJ--yH?!NtB6 zgMZ}li3%pERU>Xfb9h6$14*I!mC<6(k3jwrvF1mf9IikM0ca|oaz==!4D>jzE?qGlf{gy zyeXSpu=gr|P{WQ|?O#!Gs<`)! zz)(mr(~rtTGBrgZnu|V1-(Ji)MV0GcikrXjUUe@}qO{pk-joVKCMe41hcD&4$^i!H zPHfCQ*S`{@f9GTFyhu6p*W+ZI)N%UqF?hhqcTJm*8A*&V(QR|qm-m6Aq*T1=-b@A?{C=*b!mt_5wZ{@)kvtRE++m-Adep~K z0`?8Z6>v@am9dEh*X`QHaGZ`k7p1+4$Jl7CDjrLswf=bMx>gyFy1LfeIi9L|5XPd9 zFPN@tqeI!Gg7Ou#8xA(50GB#-4Z~^CM`~ypc)VFd+smdG3`o@mXwlQDo*T>#05E-` zI9^+8jVkUs+6SR1d%U5BKM*NW8fnwXbtCgJAp3U)O2HPM=x?3)hsQ#G(}27g5m`zG7GR=PGE zf%J(@bjufGcMrJa4aPnyKB-#TZflE52RyZmy-!2adC+i&f3Fv_KRiHn?Tw z%gr%{qoQF8Z5`UU&_e5lt(~G?2Fs9YRtd%BRQgCe>DK+`RPTJ?5m2J8_F*6 z<6E+X2PO6tD}jWA*4FD$m1Az=L$jvaX6H=`?dWwRd7^w%_lXfcZIR==Dt1iNYXfEW zxai(S8}ZHdi6!IDma!~y z%?{kGZx#=?)jV(jk8G=z$0Mh$Ho~z@&FrdzD($qEtmyO6JG9Fhf+Kr&(!6*)+ez!j zHWz%@N$VNrn7vo^CcJlPcR4ofkzWhMOLu9Bjy=}b9QrzHeHG%%zem+8iSvt#B}Ov{ z=Vg~LCdm>>C}F~Dwto8hZ!%+>#O@PQ z`fHJnRSFIN5+nM9#RtCL?XNX-SRAO}Sbwc4bDUQ#9V@8vI2fzvy85&zB8ZWgGg~n8 z3GHQ`tQZM8d{ya&FmqP7i}7%)dDa2eu(y=GpQ7fhId0q z6ZwZX7wmpXtHK-`_DKp{C9b>-9(1%U4rBL?Q%J8&(3M@{Jyn8Ez+S&ai6xSX(^2B6 z>a#eV!*8`XMi$9Hkv(P+F>8dD*kWR_3Yh80Qrtk1NolsG7VF)`5m835Th_m2hB)Z| zf0ca)c$CHSf8KX@a;YTLKtdpNDS=#ifKY;w9zvBO93eU2NOB3u1qj6?h?LL;G9Vp9 zx`2u&Jt#%LA|Q$skz!YnVgW^v-{*bbO8|lA`TxUn>^rl4c6MfVcHe!UCeebPXxcLp z*8P7?_>2bse5~gI1hJ@^RuOBV_Emh<;;H>Vz=|^3K%S9D!DA3=M(8%1FK2EXR72U zqnyK5iLOe4YuL+5eD_Qo>cHYleroKO-xQeAU@(jffG- z5og*uXhiIWdaf4(OhqN;cl)Wb>_YgT_};2weNs1CF40L|Ph ze6jSNuvh#J-rcbewCK1``0~($(8oHW4fyWnJ{SR&%RO1JL~s)W)*vUHmII|{ z;33giSVQVII4lB9$22N=_vfNJxQEz!ShPmOn!{qUyU`Zjwhu=XIvo)KIN~t$ zh$vDHJHI(1PT=(r7x{VDN8(eDy=BmxkFjXl?%en>rhn2kLpYRe5Ws2J-B*Rc!$aAS zoG_G{Ype|C9uBBHn&zIX6ldd4LlN-6()0=l`B z>zssyD&<5YupxN9RBL0P=F)&p)}OWIH(L6{JS>y!PgcVxPl{~@&-?>QPCO;>s?uTS z&z}pp{0vrdjyx@_iZ-lYiv1qT4EmgOE8v-XoK65 z-?^YCM5cL2|IY@AeAta}_tY<_Lo;`nv@ntOGdt5%>!4n;>f!{0&s4N6>fi-0Trp zMK;ez;4r{iwPnRd1Aa7rJm7kd#J2%l;}JOW@%+`?0)cq60)H(5$LGs>1yzx6bb!B` z*7-bygs=di3E@;%xY!kc)CC_$=#Ka=T;X|yTJhf^)bjs?P%H163%=!o|3#?vOZ)S{ z4{0jth-wC)XA0n3`7)xQLJfbn%J|jBuL*up_{HH@_Jd>p03VbkdYZ2B&cmQD#!t)60BE09qR}a+TT5c`zpe5*w&<~;Zr*jCxntX7) zY=Ns7Do&8EXr=0ZT9X>Mp0z-O8#+f%l=W0N%on{ZSt)de7RYIe;^Ev`DEpXdy}4IP zdrGF&t%7fkH8m?I&6?AEY)WpX^7~5phjccYE=Ne`oY&+=MGd^gbxLF}E?6XkdCnPW z=W2^&RW;}#dX-x9JFa+EHs|{3RIQfToSK!9VNG?|vh2B$BU5rmM&w!@F`e+x+v?z+ z^=Ooun8?WsWPlRPAtiFXT5GMAKZ&2aB-?ObmetkTMH;3tui7BvIhiH#ki{}U4HJyJN)O5bYCRp7#D^ElhDv>2I#>2m8gS@5IY({Slna;1 zhJ4^SkZpzD@@J8DZ9w@}?ECsuGDSh-88i#Ru<5yBghrSFM%xxMZaa@r>D{T6%R(8Z&^4 zK9hBvmsiRviW=LJe_1OdO;LGv+wlB2Hm{b^rk2r`UP)uR^R2bAF3(#n{ghU`YPB4z zwr-)7*MQrskzQ(iQ={{4eiK@yD1&)rGfGs4taAR_gqkYc_A}|u)tghc zYMH{#8_^$J(1PZB4c(v^M78qGX4I5l2%|AdigWBbR0zeQv6qs{&CS$YN#h}Aic_pw z(A3|Nl5Vw(%mT}1*zDHf&Ddh5=iD{E7!j&Jn{9!U+#IwW6co5vW8rL1B;0%ulXj9Jpk5wtM4(O+7kwbBcosN}A&cPEW(vT}rGBCi$Jz=H z$&sA*p-kpGhh@6PoIP+vh6@g19r22?&xi+8dho`>G70O<_jFXrePBi%ksZaWWr=DY zmHpJIdR7~QKa<2X<=F!xrauzd3U~Z|EbUkjo#4?Q%YAhs&E|;Mm^jQzb4*NRR7_lq zd8TsMptVNrIIa^-!F;JBnb`3^Ii=cDGR#tPxlSkQ$wNPpb;WFDhtuyQ#1$_*Cbg}{ zI*qA~I#+ecfG@EiS%#NB9@B({%5wHQD_^S;9Tsbj42y`1jE#$pj*5v1i;G>srC-U_ z-0z%>mn)U)3STIo+BjMwPk60T`s#u@*UUu$81U`T#eDG_>BaA!M|)TE#q;tCUi?A< z7nQZnl^0}?f~D#zUVTB{;4K#=z9r_obP*Z_H?8_!mPe~SrT+Ce<$UF$?BEQ&f<01Y z11EnYZE(uA{#L#!Hh~OnzLjP!iKpK9qAy6WnFoI-GsG776Ld`;+=jxq$@em~HhLBv z7Znu|8yyyHh767{$3|{dKF}w7_uTPZv<~{%Kb|Ci{k_b4YVx;q1_5^@Ie7JTS)KiV zmq8^7G}fql8@KpT&cb?Y?~l@55j(ImJ@lsB&Urt}7V>SSQ00`HavKl23gtcN7ugj4 zpyI{5I2DZa?{bf8(D^$#`&W?Z(g&K!a06o7a$S01HfdPzns7)U4yUcUD{Jt~2h!$qLfP+1 zkekj$9@KnMbDAzrY8M*|y1^S1Y6HJOprGG0-Q~&Os1&1|asm;qh|jSLJlaHmahp4` zf=6OPM6BhDGE`r8sw__9Y=@S>C$a-fpF};ym(DB`xhpuRjqo3Bklc-|XR$x6mj(gp z!3ADK;v772dn(d(O<8;L%ia{Loabaex@jovZ9YJJ#k(p3an_gGa-l!rMzpO+ThR{< z|JsSPVTy9mS>%sqE0>%|+Csse+s~EgFL6b=%~vbYFPawc(0HM*+{K33*ygKP$f*Rw1;Trfmax z)g@VhlP^l?+*F;~D!A3}ml||dUd0t8To6R5ydshsa*rSitopP9ukoZHnux>jcZ28x zHg)WLA`9~BubR|D+yEaRtVIz#DVQR~O?0xf7De(;!IUI!;h3@xW!9o(1gkx?*0p1w*5rqi z0X17wSFuFxVjwrQCNJ^2y6JHh(%F>ixdp%Db)%7k?ZqP|7HmZ<=E=lggof088HgRDh<#=ya z$2~+QRl1sFT>LF5zo6C|$Grec9ZR>Wa72VdVyoiQno-UU>hyb|>n?zMZHd zoRxUCiXEsFMoBWC=|n-|ZFPi^=Y`HxO}wLK7;)P=Q%zhZeylS@+D^XT83XleOl0mj|fh5VKGd3P5YOuN)HFzb$P)QGdzQ!DJe7j&g0Y#AKyO8ubK8gwJg zp4`|Cb9cYWyty0A@s1mtV{=$7xsIFze5E_Jh99w35)Ht{-?$`75g(LM_CXRg6CbKc z<#evX?{%dBQ%lNZH0=D4bgIP}eJBf> z??N9+q~q!+blt4EF4|<1Klkp7{-5BveaS9PszZ2wUkc^6XJt+9+>fe4$65MOu{fos zl#7T49RDKui_g^@4b_w45*&lJI)faU}T?oMaOBL2#XRc#|P(IWj zn&}MR?@z(e8|tB}2)JErW^Zd$j|oe0?Jfiv@!rNPbYm9yXg#4klmm zBW?%NTqAsMFnOy#sr=R?>h3u_(~*#3`B`mj#7!82l)tEk6NT3gp$g)v+EkCL6rs6P z(lRV*hWq5&fAKE2%CBm3qpUCsRf53|2CK)zE#xO|XnkA(t+c>G!Qv*`qf-_=_ET@E ze8ECf#c%2W4&6)x)ZbMu9ZC(o{?JJb10agqF9gZJnw2;xglm9?G8(tE$5zESI2rrTA&kh?w4`OnpDV+e{mH``8e^# zRPr)CP~-IWRvdys@J^!|qELhzwP?)6UD80KB6!dBd^)Pz>+o?Dr{P-v$;@dq0=oV| z8ol5#MfB0xh!6GyakH&ppjSm>En_{;s9Z;y%|2B$)j_^)B@es;bjJ!cFj6~qRil;;go7B7VRIl%`}4Qm}ZJLI;M=eHF5rL)CjVP*`fnyjKD1aV+1vT#tR$? z|8x?!KCc0|{_Rk$zG92R3GKw0C1!At2@OeHq zin@t~;w9cTi4u8eI>gr^9+wWAq(n^OyBU;kiWmXcr6}aTGiW^D$bgG*G2h9cSH%)! zE7Dv7YceTLzAlz42B_MReW^%vMcSbSmKjhKXV@V%mY2naXHi|R6(Y}QUpOn7G=%*d zLw>L1PqQGXS8>g3%J*6=x*16#^e!C8rUqh-NY)s(F4wj~W};Wbl}E!~S}O)|t!#8u zo2WcwG{uNF#E{2P&+&JosghVHdOnVCb|MCnRly)ax=8~APxD7=xo ztmFh5NFw1j?s$*<}EQyPs5w@$QSBy zS02Tw+l6l7s5=C2kEOw0Z;Kd%1_Rl=9NoJx24s4Ne;q?bP>NY&X`0tg@hl7LJx(4+ z3xpH=(Rw1q^W1S5?Rk^P%N^CfFJ_lxiMx(Re3OZo61&9A$MIdnZZTCuwdZr==>@3K zkO?$R>@`X)Wd8}UjP`NMi8NR27kT(R3b<9DPk&CNZXO53NUaA0jD_&_jnbdDC1Po@ zY!Z4>%3CK0d>I<#Lopa?CXU~E83TMs4AgZ_odT+CI&5$T z&ntjCp;rN!>4@kJp%Xe?_Joo>UjRGqBWzE>2NA_D6_QuM$HHBw`fOEOvjhUpAeKXEGx%x zQn+K%q>fGdOa!D^b5nC{*^aCniz8)NhNsnHv*%iK9H;ntAq6)4T=+jyq~4M*gx@15 zt&OL-c@Z@V`ceegvMkn|9F%4JpO%xAZOIyIKZDy>sCxCYqDnS4AS`G@PS#k9_KYg6 z=Xg~SSn)hI7cWnjHAu_x6`v`hU>tP)vk3QUf6dh}oDqO#WW>~Oe3;SbG=wG7X( zS}$?e$@Cl+jgHCGM7hE%CsVNBH^NgB@fPjBZ$%(~JwpcieJ4Ei`nCVQ7a<%{j6KR9 zL<3GPmiVqR$G<{N#809w-zb*B9(reghP(vSRQw_u>S)d=mY(rfg`X}?EubSTF$mUc zq9VeqJVzM{kH6|TkfIES$LpdFADk)c;4>!)Q>cM@Q?P9c)mLu`o;wArtKUR-TqZ+} z`694we-~9Bp|v)mdTm*M2rsSHES&&nHAWi`%{H{AWZo9s{#6PnxFft>iE|w( zj=%IqX<1Y4jts43!!!OCK1K>#Zi>T^e^=)ZFx5YT((*Dh^Y00%5%Rw9vRJZ)XIQXx zlK!vou^5ffiTpsgXIe8;GqVe&w}#J29hqa#%P5i_1sa&0nw>vcx@-9C{8#jcw7l#o z(kTB`>1nZKq&jj^?735=ht_68R(s;DwOxH7ISnV^U30OO-DwZ;LSWfy(>7J=i z$63-NEyH-6Eq$}GnPIi3S%&CwbEMRmWv+Bj%g`R?N%vGeV!kwiSlVvTYtlE(mYbcG zYqg|~%(JI2kQD&f##zuY%%c(Zg|ec}?y%hNi}>BCSd*3T<*C#M-n*!2G*&E@ zVLW;|1(r-F4?lDG$P{~82443u)3s} zhQeE!Qj8VoS}rXntH&F1z*A&gWA}t;%Q_j$$#YQ>d>`JtVJ6m;Z_2obR=sCF{Bx0( z^)Ok>l6i5hS#T9?;Cr*FHE(|%>$;7y2md>p5_tbC=!#8zY!)TJh3Pk&I(lr8UGxPS z7sbNT89$pEi>ve8qEu_zCFseg_JBlLa7yJ!})t{AuRciWmf}= zUIfbhk5@0E7d*|D;gAuzBR|o=B={O{E~04hscfkyt@3c?cwFjsUH9BPV~ekK1XbXL zC0PHQkZrlvYHBM^$`%?q0@1$i_xPqk0!2UjWeh+4W(p&h9X%8<;Lhtj3eZl+2-6jhUYlmqBB2XM3)>l)OBy$ktkPe{G2t z$U~Q5mh@fSoqj4FGn4} za_|bwlk1$hf;xNMkP$}85e7}puYfGLDf#*e_y%vu2)?_TB0PSRJ+xEzk zql!ywsh4y08+0M4lDn@Wa8yIv?hH-0=F96I&iLKrqdL!g0CxyaIz){c*Kdcz9p2Rx zw+h-8cBr;L8meVb+!W~c3$@YyZJ zO==tAimE70Or*5K5e@GOikrJJj})Qc3kV^HDY#)Xe2zQ2emg~KwbXYD_4ZZVNZ)H! zNKu;89)YT>@T9{ugKHh3YdA8HvX?4zt)tW{;B3PRsacuXS#~TDL$%W#N@)e06LL;F zO7SM=)#H>Wu}Pr>F1V%Ca8CN1HY;Ar8h2cI6YR(jVl z3l0b(P3aw7KuUn1C|yzUQ6L~FB25Lw{{7C(y_?-6_~`S#{@;)2-ZJgXnbYRXnYm|Y zR9*R2)zv#3eDyw~*m19i<@dMe;~%b0x|;uN{lnFrZNZI^Vf`-H&)HqJXj@c_&E;Y? z8+W-}He8sE+c=M++t_ICiqd1ESVRnS*&t5J^H^izj^>e0`4MJmI0M#H!f?3}SOuAw%`9%GcmWwRzm ze&sqw&C0Q?DekgaGcj*jn1|c4jiHs&SdLgd$?nx`&D~z)Ds zda&QHdj||2{K!4FLt5l(7-b)GPGdDQp7hjxUVGxW{cYCgb#{P1`}D}Lm)SE7ZejmR%xBiSf6n*GM6^J!f0b$ljY&nNL$`E0(4&*Cfj2JYt@ z`5*S}`S#29_xSt#XZr{IL%yHy;~(>H`2~KFPt~sSk=hlzUmK;()DCGYv}M`}{+jlV z_O3Qp+pWE)&C}l3_Gk;Vz1lwQB;T$TX$Q2G+G6buZISi`e_boqc53^zx3qQIdhJc^ zpteve;ajwg+9qwYwpDvuo2G5kZrGr`s_oELX}h%L+EQ(aHe35hE3}WXkF}4pkGDT> zf5X1kzQ(@ZzS@4){*6D!oC1-Z9#SR6S4sV6+65}M)My2$=YOW0lZf8SVLQCc{7S_GY78^6`-IHHk zR_lw+13>!Yxi-||cD1xMQm<3<+^)dum|R=B0xAs`M>deo`=RJE?qpxrs9vRWnwYF}T zSMz1%5&BW9H_T_}H$*dIXJ&)gD)cJN6YbVAZEjE1sSUEUM0bMGq(K^R+_%9%XB7aK zYfE%H+*OTt8#JjTaqePHx8{!UF?Wo<-EcKbuA%4o?CvO^Hr#|P5)UpwhF$?OI2Z#+Sz(VgW)~l?l6oC2$C>CZOKP>G}_wG z@IH`c&8&3%cFMZ5ip+u{F_bNaO1Wc^ZBtggYKsd|+UCTYXub44R$pxVmX&j@f;TpD zgG2^3YEh@+nh7EzU9=YNa|{Pk`ZcK&s7zCciIQz@H)c1f79T+XpwVDpjAo7h*Xj?i zsQy3xx9WFChXVdylYTYh!IduVbh_i+u0g)6hdmnI-4JafLepm&pEa$~p^`fegb7}k zSahb2B)s{q9^6r2%kygPglujD0aecCplL{ryfmbPo;Byk8jHvWEkG#QjXgDcVwkJD ztK+w&yD5H$xJfd-;I8gb6-#;KwRecN(_M<&9-tb#G1(1KXq3njKfArSukOiClI0{# zfmppZ;*)T&X|wtC;U2)<(B|=k;ax1=K{4Yc0BUH9_C!$GbdQ6&mgb41 zC$}d`cGM#Ucp^9&Y4KoRTyzLQWN|?V;>qFRQm{lbpUtCTyh7meK~~WqQck;v1=8x# z0J&K^M=Mm+MFyZ(h(6JW(#c( zWA2cGBn87BNm5gTfFQ{d51s`uI)DW`#iA*RQ0|T)*#M+rtUwPjQZQ(4MmP)zhJ5u* zQiz7bt3jWYX_&h;64|IPo2LQGStO{OrGm}5IqgyjUkL?g0 z0aZZC58X!f1UKC|@&E((x$?Z+Z70^#zvBf}6W8EbPP!`{Xmmnnxtu1F^DUN^?-P;iG6$9WI0h9} zo5Skmgt$=VAZAD*aNW_}qHP2Qpojs?<^cNwVjZtY`pYqf@U>fGoNr1LIl6AD2!kAe z&IFFgE5#K>axx}J4r@X80R<6Y1SzP3yW^1E3GNV}&i`9rGYjlss0nF+I>jXw#a{pf zrU&pUX^!P9o&jb2O$m^8&-C7S=GE{F6upM=hT?6OqR zfsy@dT@JfemqU82)YUcl*hz0_71{>&fNc;)VlNl^A2kGwgK)Qc z8)&9&pvi%OVM8ib^MolC3oQl~z#O4ebs!;MJ-eAHnZn9TCaKg&ZNT731Cl`*Fb<+i zje{7e67yh?0TpdA5HY54AQff=EIRqZG7g|qOO1nk8yN@CuB0;p#z7cC?ySH#kki0` zVWVb+?ywN!KvTv6rqFq$je!9&4yf1CI0z3l4)QTWF^vO_?m~ zApOHI%IX#Dab+B!UzTwYWSd}kX_Vn*_%q{xS&9gvD=g^VX5EJ{0*RzRcLK&iphIq3 z{@(+;ydVSAgfu{H8V7#?kRanAKM0n;3yPZCy2=>`u)hf?O#(%l$n8{BJBggYEJ4OW zK8=oi=XPG(H~{t1;Aq77rg0GHCdR?}pK%{#9E9{Z-v$8xD$xK`Ii3HXbvdNRunj6U z4nn&ujf2106%YAUGre{2JHZ?5Oid$aDj1{GnOZ?Q zxH%*pwUl_tqw%xXn^q)6u#?-I4x2JVoUt}40J*1Mm5DDIFDLcmB_oU}0`+%rlatntE(Hq9)_GRx7-X(WuQt4oW>V5yHZ&9_^&iJK$xMn7Yew2EgljCoa3 z*(=7bDp}~(4^>)*FR*JoKip_jwIh4o7+19|es@>R#_u0h8#gXkBd30jx|%HqK3SU^ zca9vMNO~Lsh4~gAq70d~2cRQkIJ0qMXj1jgMG8i<_bLxLgmP^U&=fdNpHBmb+w?qr zfqA>zX5O1*>UE%U z6UbF=`gKg8iH2Y!*~t7Q>rX>RaO3ikKLG<_v-vK+e_}zz4P?=>XaArbftVJD+utOd3o0E^F0(m<;M}z;4UJlOD9O`Z$*c2ec)PvzkuhMzC7n6s+GFfvIUH4G;iA2{# z(5$Yed&6^JPRo0THzG%CuI?e%6z#)Y=#N)m$${kki^$=FaS6}i!*L1A;m_h?&jFyg zqPYpUrh+#MY%AKi#{!?g>WadE2=Too1qep%WyLlGXJ1;D0nBx^9y#PQq`OWz@Gi@{ zwmDd&l6U#db2uEZl$@2r?Qo`&F@tAc0XKOv)*6=$N+g{&MVaj1B_OlE$w5$+mMD`j zf=^6wW($b35D_zD)ehrQD|^)$1h>sOJ3N?7S960Ubj+WDakWTuGVU$XoQ-?mTu!oi zw)~yUEz;zfwz=FSO*2nAmQaa4W^haHlhju=1V|I;`P!N(PWp#bM_G~^LjO6C|7;mD)0{Mg?CsKS;7T4MmDGpKh&50BrQ1@Bh zNQ@!wL2Br^I}&t&3#ikD3y3ow7Z7I(E+Eb{T+m*In+MS(CwiRBQuE1+ZnG#-saA_B zW%X#FuGXJgkarI#>chJsiX?IOq#{$?6);7+7>)5jSq2fd%?mZ#QObAFm!ehkE#|G5 zFv_VOGNOfmV8Lju{!ipg*w*H9*$vqXG!&p`*^|FO&&I`8VGfg=Zx&Ow@Txb1wt*2@ zW1i$+8{x_k;F*!*ib(5_f<*(9rRuh%`4Wxs{8Utc8s@S=UY;iJ4Oc?9|7gpYTBHKP zEnr5^c9nBrcB4gzjTg$%EC@`ZTJnO#C0N1a1krsmuM$;6Fu|Ph%{A7vOU$=qf<$7l zdBcKmh(4O~fR&L4Gs8G9U8-7L+S3psV}v-}?2sw+IIXO|+)N5SQU>VGWdeC>ffso3n)NwUCdr}h&AW+yZ`kRa^<)h1TY z2gA_Vp7tA)mwv(-)dKFmQ|ItxX?G)diN@%IF!zn2>$-O;AD^6L_vb&yd+% z!(_xCIMaf;$d-m?3vA$`v?!Q|ka}b(8ZS0W1yikRMiYcR%~JE#1i?m38Dzfze+8!f zg4;r(5{olJ22q+OD67VhP(Zc`jMLCuC@^42bzjrc83ys5{)TLrgCWiCt8`*RYdsCq zhT65)u=k83wfoncDNS*>i&1t_*|IY}F#6P~?z}?p-LU0-BaQqzwUd;dp->GXct$|c z!YoZ5x7BGBBaf1?H6$uP^BP>>VemFaAYKF^Ao?q^{Z`_T zoyRvb*UqK8mDD2`#eoa76Lcngk@_miZFBy}gCw_cD7!Ly*BH=@q;`P7OiKjKngnLB z>|{(As%IyH#7IO($XVBfQAY3VjDUzm{?Ne>60u~rT|>lTK0aG^WDsK(Gq?wN!xgca z-GYmi`V+4DBcCu2cVqFn>F+1fK(W$Q;08zNzo_T9($c@is8?oz-;Jc35La={Sh0U(>b0u-5_W#w zZ9N(=$0envON>c9(^3o8${ARR;yF+xB$i3$lT}i&@o~?Z?7Z&;zDpu6!WR$ExI0|mW$d|mC_7-Z?%j+}Dl~@oZtX6TID=G=zzkUVK5&N> z6Vt729O~VK?OGRhOC|1A49q+XB4fert=ap=iQ9A8E~DX{UD!*;#Jd{dzUZ!w>;>cG9bGGY zf91}K-#hL$-y0Ek#~Q2d?kr3D9FPJfVEPw~cKtE{*dzV!K_@=!R~t?L-0y+dVu^T} z2eh}x7~20Pq!;zi#P5avjoCuOGoVBKDjHr@6Huao#`W3jo*pog#Vs}m-l}1*;kxHN z8naLCX=}!sg*n|xC_~u{8LjS%!Cja8TCiV?G52L7 zZS#E;B6j+|2KbG?KQmx->BUBu`$wWD`|dAAK1HVx@+&<6O?Ufovm+g69mf z$(ZqA{R-Y5uJEm%S07EZ%1guVfV_4P>s_KTbg&N+cirH|VPC--wuwWpvv}k8!Gz_Ehn{dMg4L-H>sCHAgq3*_ z&Wwgbo??59B}2NQ#E(Pjn_*B+r_b)w^<`=h)VZ4WO=)9&1@+ zKcRr)P&Fyai%o(mBAy78Ay{XZ}SGsn0f~OYdhrba_HOEmlu^)YGr(spfN(b6t<;wrBwqM$uv>P?$23 zA`4rO^s#S@{EB?!<=%hP`}7LRF^SVU+g$)P-Yw6cx( zD@c)V$B_ToIkr}YqIfEUg7H`h1<@05cxsvlsS`okU)4FsxO1!r#YT*+%jOu1$9fb! zVbLEQ0p+^LP`AsD7p?nZY>dVZ8Q(o$i=eIc!u1g2cfBwH>fr1PdstkNL_J32pc;cS z>)x4gl}w>+6H}8VMj*5~;e)#1b_(yn4pypvzZsMH#dlq3>UJ z&^W<@1`)xo7{sFK-Oy&t%c{+)#%C|LwNNYL+9<#o_(SL%bWjs{AeK z8k#13?T$h#i`T6Y2TEJ9o7^^O=1pkj*i5UHWv|=<1hQH8v6aRHv+AHLlV?>;P?Z8X zaxoWfH#wf0XC(o5pUhfaeV(KNOt{h!EdU!j4bcKpjDoLL84G8p26RuDDgHj5O}dA@ zI@!`aVWfM|8PYv=rF%$F78$EwCEau3)f{Mv>*n6bCL2TNWa9p{Ii1)<2w!5#BjJ743dC|9Y53VS$?j1 zc}f&Sv0+tAj97rFx=#s*lhrl$EJ(xfTv||*EjRRqNw}}S@H)0!TnytiT#`~rE-?KS zF{v|4HdZavk)>#19lm^|*nU4p0kJxaU$aF02w6w0Q@Hmq82X}>y7b+{Acw@$d(8a~ zq!9bPK}v4L8;JfbO^_QQ>_*}uYPaqpiU#hwD0uxYKCl@APHo9aNJp-@i%33JkQeqf zdrRnJfa-0Cmcr{>`2?$K9eB6Ve zQb=%$It~Zlx|Y?izFb1+fPH~U9`!nc?yR2lUe*#rIC)v!&IR$m%g6fItsGP(b)&#_h|RRSbvQsJnuU zz2+kb)Q2}7C7;mhsAB?ciR$kA(5m_ayiqULU4aWq9JG(JuM^$6k zs{RcM{gSIsP$wWGIV}R~Y8*9-qf19>>gqO8Ruw>h=hZcX+ZwhyH?G`hh`9YMAt9WU zVifFo%je+^!-)KRAXSUdmmAS*l5bTA#>hZ35HN%lS73xFL^^S>wN8sb$_hsTtNj|8 zj_3n^vBGo27_lbXGJmPXt!wH7vuD@Pd^vnAjdsILWk$RI`^m<*wUv=!*4o$Dr$)SFw0AZIKYl*`cR}6u0i9pbg8@cOqqb4h)J3#m&)(_}*`K=(7Q^+s!nkdKK z9q6o~aF7JQjUhz2|U6JLzJATRND=t~alD%q}H} zDn{+M2KY_|Ll+kUN5BVFT8Hd%oQ(bOokEwLm}2aH3tcW{ZkNAll+}voi3o7~rq|fNjr6ytfz7wPom@Qt?iGo5?4~4ABg>RX z?aJHPQFGOBl8kM(`Qa)dgaJ%YUj3x8adYF~5xTNDgpF%#xy?dWAf z-$MN;*+TvJbqn?5*==PQiTd%}*2)=bXu>Vb20EeeXR@s7#J`QrTbl(pQyyIvHlxt> z(YDvvXU1bii-4}`+pAW8T_Gb}SLlj#MH0G7B)X0nJ-1)Sm#r{f+)>?Fx&59{RKY_> z`GBfAJ3_?iojWq4mRUGf#GDqJX3F#0-W_=sQcE=zF@vJ%&lF9Qh#I_; zX!_#rWYcs2&<I3~#n2ZD^Mkqp5ef-QA@LqNeS%_vZwyu!DbQ2H^8Qxrg3)*Xh^S2gA~#C*X9nk~dSFF) zN=Bx5QWns>`q5Ha{`ml@Td4OtEz(6E2d3A|g9MH<0D)+ycK5~kc|2JXE9Z9xPgminDTqdC? zGTuHs;lBm#z&{Myicb`1!vWx`BiBTh`1U*a>Y_irMPkes2}4{?Y%dxsKCJ+^D*MGx z!|VJxW{C1pQvnl4Q)%)ehIV3oEJ4Rqkd-1r_%LbCjt4O$jHmw985gSTSBu?OV>68% zNH&Z1N^f3Ib{lSr<&8c=45@=1=U={Uy6s*S!Z@y?5WGzHr!<8u<36R?kK!n zGNP%cwyAWiN|EDhqsXntnxkz0x-74J6~rMG*0NpIg7^u2ZBP|W#2fWkzwAFprQ{JT zGRsv}H>UBlF2?k)Ma%lEe&$6QOfoWQoywl4phn=`MXO)TcUkp3QvXUA^;si!Q0%G? z=sy%$8LSKYSUj5nLis?f&j40F7QfTe$09ou2Rf|FV3kCECTpkeKExe6B&sxEH?xoY zc@5Y@j8KuqYKk8lvPx0A%wFm%#L%X2`At@!V~?Gfo6S5bZ+l##nz8HhgD{(zr~6Ih z!zBeNXd?eTRUx82eeOJcLq%B*1!1>IA#E@PFL##Y-a};p)vcT~GaG-LFa&t>%1})H93=d~TDSV|2PpG%xV->;| zJfa0T>{j4l$OiCKOfJ*e3Q;qcrLxJQesfj}DLtFBR=7-T&aQJl(N_m-q9zMitV)06B zZNpk7{GrOY^bg51i;_U`0TI)dRZY08lANJQukGatqI+8w#jc9m+p-+z)z!o(@R?s; zEoQgHSdJAt+Oqp%c2b2NDZ+W`Sdr~xJ=jiBvm=Vn_OY7mC9&DZZp5pIc97jWMT>SU zjTej&{n|lXj}uGUu_kzXv>khipIt2mU(aeZxg?u{Dm_IA!@~J!!UIBlUZSfNn-nht zUMUXJ=9?t8T#wo(i;LH@3{;oWo+T;QDkNN1M;0#@T+b?txgA&)@pOBb)jP$w_N*EI zZnfCj9so=h-?f(oYj$8MrFo-^M;1}1xewxdrx+TX=tUhnJFs*%Nu2Az64?~->gNCQ}$)LNbV@Jr*~v2s9_@N6A!6ZPj_UV{1+7_qV=PMWoI=gNmI4V zP${nRDGODKvwX@bl~SpE%JzzDt5aa}?Ym~7A?=GFjVF*7m3kqYG^e&Bc9)sM$@jI1y78y-fZ@%AbO&6T7pzW!p`s#0(_%tsoJ6 zlFnNpaYlETvd@b>-PstnMD*;z+SE`BNz6lQQ`UN91WueluoF%b5G#AIs(}?lGmrmp z4{Ns!p7^jU@zd5`8-> zWOBf&h{dWL~PF4l%_41u8wOrl+D;}yS z)uJKcx{FmSS2nG_k%3(+|4{y6vJ8>bjko} zp)x#lHyfKdjZj6QF&c5Y8y7IT-DGsD9QL9rK1ao=Y=^&nKlUUK@M(k;YAS9ZCK?FG z3w^P_|C8(piy(hPjQ*l%G?=wy)5XMLP(&+5je$^6MdIKvSb_3oiqIc|>RBnSdsL=u z9mtxfC4WevK*WaGk{J0A8^nJdDPo5M<%`9F)JjKVs8C~1KOzpN87 ztmD{;+u`7`LGo9Bm^J?^eLBY2Khc{%tls#mJtm9wl3UK;Q zbYhh11Y7K{^K39q8A5DJ6$?$^Xa}?yIf9*LtHk~J6;SHw{A*F_;DU-M)oeoe3qYx( zB^or2Si%nxa*#!qjF1`{ z3`5c_oPu4k@dbjd$7mcwXo!19gUiN=iKE#MrW~OoGa$`yzCt4Xkj|c62orU>*k8!* zE0+mjMaq`xItHq7yLe;_6vlKhW(@NK^wwip!$c)nO6%3B2+5Yhv8-viRGgApvKl5me_gEDtgmq-wWkswOL1%e2t)<|O}Oosaq@L>x04ddB-~~nt*k8TCis*64i)<=; zMYNj0(&K+qrsZ!oH@0x9Sxu36asouma&deD3-b7qmW+IfWk#yGlJl5dteFBUf5{@u zefz(}BGE$LORPHkMT~!m?L$NUJ`-^cBw4GIFjxIX?4HCZGE*3n*#mg{>tyC(KZzPs z*d6@zNO4_f-bAdN!ZMNb^C_t3s+czo=$tU6sPAkMLvYTMN*e)?wup8J5!Z#C@EEd*#hy@E37G|&MRMGL-FLE#k!-lA+y-K?3};( zYa44s`5JLa(7j>0#|F{&4f8n3aV{H0Oix7XOMbiHLRn?%y%)$urS7rrt1lp!+ZlRl#@wJRYG$YC~LI9P=V$ zyKugX>IiR(^-bB8Z|AeSFc#exu-$~=Y75ypD-Nx+0n)BiwW#sBba&!#)gI{T;@8=2 z`O{>V+=z0q0URI4X?b!_Irm*&Q-sdC<)b-AI?ixzUn-`Z1vsT9u-S@sLOHyMM;h$0 zW1mPE5U=@`ezf!>8;(JdW8LI8fAS4ks^$H!e=jt!yYHbpQS_J~@6)Z|Jfe*rEKRB+jvoA`k9 zG&C9`qx2|}PpTS`@djw3NDO#`^(Zw>2|2VOm$}I;CZ}2u;6(&8b;+i`;{W^&)`7v` z_AbWM?|FZ(#c&B!f1Yx{eUAVkY$XSBaTX7W7)LZqeO^pk!ro!a#2rf^+Ka^8rOZ=( zX}}r+mgv|*V1a}#1*jK^V@shKmWvn92Xj0c7wwm^2C)(x;FqKj_N(|uE@QtED~?#f z&Sz~_gnNqS&E!JyAqZ{;ws9m|fhOiWy2J|inzfQ4jz?@;$?90E)EE_Ua0z?ipMzP< zTa9VdY4OEs)(4lCYgi{-3fBO&FNpWoNKX)3iy82X;?}jS8PX=Kl@7Kq)DC zTt!(RD%W5-0u|g|u#U}52#F5>wZLT6Pl{&iS%38M<@H$BC=$EYLpOXNzFaS-S$Dh% zCXv3oELf2Yfd>KSUBp|GjoQ8?orq)Jg5W+a-g^t~^cBM8XSd_YT@07sSU-!!tC#(( zHC`cmP4T@Sv!cmD-vBu&E6*d{k6r@}Gq5RHq=opn#x(xKO7c1Ub~E ztSy4@_eGh4--VpWc95(gr}=O=_+hWu{2qp8nJ9TrinChp2a7YZ=9B=%9dRFtvwPl` z;_S8egT)z#ID7B?U~vY;Xw#R8@I7GoKZ^+czc_0`4BZF8vr-iAW3A%$Dou*DPbF+G zQ_xEirsNs>S#lt(L@yjAdhKVcptSh`HjJGSj~!rja!!>kOqu}t3K0N4ri3$=0n6nQ zA0A*C6|}4$WJw$?WgKKn(b6XeSwW}>ggjFsaPT44-Krq06!g7^m|LAy6AHn(vTzFh z0~Q}w{&mlfSb~f~f*T)z-24GM&|;UR-po)gnAj8`am-Sr(#ywv2W-}A!89{wQ{3?( zxaFi6{UICPJS3`+`hoRE6}D){fhGZ5T`mV{!^2R@C&ev?**&4iz&1WviP(3T-IQIf zAVCdwq63Zoq;_<;Z$d~+p#_`R`Vo9G#p0un*gOl>p;S_}4*IY|Vh7`GZjHlumjgxY z`j|D2F;zXMB@*B^>=RbZj*0g_VM&ev#?F1hs@0HaAuBiq@ME3NM6g?;eV0f*!d?u; zM1U-I9%0$pzm`Rzi3t{riE^1l!%tZ^tDp8#l6vV=R;w)N^FC+0*>RC~6reA(27AKM zV9;?|uL^_7zzkGc%ER<`#LlDa+47)c3G1ZjeT?N>&4;2nDQ&yNaR{9h$}muOP)^_H zEH$qDYocf|=s0tQ@bdo;dN85l9+ZDwaSuivXDirQikyA|(r9rBdxTvOi6>ayxM}4o z?sbCAFOOS_iR!L+Xs1QQrHojv7D+{RSk6KGR4$Wv;3P{S5jOTD%)jws?@6pk?Grzp zWY56E@YokH4L6CcU%+r%F3x`;cMC+GVr}r$`BYhj^8A;~8%1YtnkyQwoq{fzDn2^J z+M=#1r(r4;i592X0#teQG=xN;%9>xwD*Js|L6!AU=*=%NJ2BZ26QDeOxo~`imAoaQ z*H^4hduu7d++as?4m)hf$pB?)?rNbK6R6MD3C2FwX}GDg1H1CX`LEzjSuCoYVfk#1 zc=-&LVdjf>&#)%$K#A&ezF@2JS$}&9z$V_DyhWOTW2xYu>0xblDsYG&?2! zd%_LCmxn*#xK#=M>2pA30Q@fJ|Exm7p;_UH^K7?x?IM=jzYwc0vINH|OR_*ASK$y5 zhrVUi;%1kj?im!6HCA-jxXw*hyS*gjX##wl5zFFt7Xyu9E`r4M~lk8!k19<&*-Sms06-UAWb6Y#l5xM_#(HZdat z`{GXfKZ)S|wR}@GnAT>5?;}-9Wz@1>)l#H9IsZ(K{Qo!2T(gz@|Hu6;3y4_n;_V{L zK@f);XqBOV`QTHG*8CU#RqY+&35ryG_R_*;nT3|+x#_S_}x4(xU{>126uwa z`=ii#oj>I`tOgLP*>Su{>C=)pp5AD=G`RH{G_&@Qg+#>Zt7%P#%%W-w8%^6!3SNod zYXU%_9os~w=DcO}N}>)j_u=)ODkeAQN!A|PabnG3EkQI~!W{|lvod+)x#=@g6~t8L zO<^2SA%J{$Wxk}!IZ^|tjSi0@p2CkyKPRfkBMV&R@%$qVn2AZ0nCYLT)e$`td8;z7 z4wZk^vs&3))o?Cq8P~)i>1G%x_x_6CtMQhIL(Wa&?ch9lE(t9PF*AwR#>=84J`k5$ z$^4;|uchLWAL@~3x8np5b#RM4^(C<~nYTg43(5Q|@}E4C$s7A4tMgsVE$uZhB(@Ms zTg?TVEDUoFm{a(Qm3~BAsKKkVPlT%`|HdKR`is1}SW^elT~SSW4S!=d$LHgU{B1k{ z1@rF2di;WaYAPSgP^elhS$1d!&lCe|@iTv?wWeZgZSF7MWUjc$jmB?l%Ik^=Zk{Y^ zrSq2*0NvQj^>|hGjUPbd@{sm_rG*W2dk(}(bY_YaVupUE%DN%ne8~9kBaOUc-LN>`^D8XR8;6cH2%NhTS_gthJ(cK; zluFnym5WlFP!-=p#s83j{09lIN-^;c+<7;`)7;w#;=$D%YFo~`%m=d zx*rLu!XK})04d_&#{fB#fN;>)?eJ|2km8)5enIfTD?i^9pke+?DGi4f#1kDf#N|Ol z!8APo7oy=RznU}*l#c!NE^|LOO&!UXDg*j(XPyT!^|uQ5CEoWNor^R5HyqCUf8rh8 z>|zgNu3A5u)v32c>JY4E(FD~rTQuKkW!PNAl-0L>gIruK63<4nT}c~d9By6AI-L0F zX_fJ9UnTB{Vb?eLm;^Ksk8`zDZN7!7{QJr>q9s8y;joz?4#i+I#9!^j|&^9oJW!f+scLhq6fl4#i9ig5t za(uob6>St?Ge`-(smRmh18+-zQq$7*73gTP1WX-G6XP9JgbufY@a2@x?!{*@^nLVN z7L}7|`kG$WU~hPyTwPDe^EfoBmWYO&4TwX?w7KzZ1M(My<*RRxg=1s9!{elkvLdH% zFfDg_UGxDkmk&co9{}@>$cxczoPii|f*iJW;jmGJv^+~|oB)b2<}^h+;{alMYqcED10F9t&F&4SSQ_4U%1;P z@q{|CmmOHady~ATh0q;s#CFRAqLo@KqE1P)MdK_8`H@76_UFD?gmoO~XfBV6$LGaB zF0|_&1LBDRq7h_(+Q@T+emtO#oyIn>!RQfQ4D{HM5<@H4-dG>U=-@Fz^?e9}5+n-f z`;#0?^{{qth^CqltROprp~FT18hV69(l>K}EiTV^#9Rp!5U##+mWCqO^pDC>kxN!Y z^?G^tXcv-jKg8`4kH@o&{Dag}qF8*tjbbg$N^$lQefKiBQdtZi$t#5r#tgdQ(a}hx z1#J}Sk1d7PjRW)J6v#fD+ik=54IRV)KF~$MlbxgS;hs3G6re|L`PI@moS;M;5T)|! z@_}~dPa;B+-+Ht9i`GHm#1Du7fL%{SQxX0m0Z}miWcvUL3>fE=hX;cL@oet`@GSB$ zM>#Gq%1D>6L`%R3-q;(W5l>z5AOIdjJ#~&|ZkB0#QpyCJxovjcf%kwZf@-V{S->HV z+b#I=knzd#BX4CwFeCJW-t7Ho!6*AaUz^zr2QEz+7SCOFkz0i|@XS^e$bE|z77-~b zKHFg)bB`uGz!FBOI6op@#J^Y__Ef6K0t)mPvT^1?2N-hIKKUm&8ciS0(a=# zXnCirVt9vhrF*D%HYyZJXI6w0cqs&BrY(`6#0-l#BAkf)PwS-i%UAlJN>x}0k%+Q+ zl4W~=oj7u6uOdpui_hhYl+qWHC9-*bC#oyg)|bNd|3-1dycRr}%>$*eI`ub7j}4T@ zshiX|q~*1Xj|=2hnW2aj6$baPF7Jv@@IpFw@&FO?D@=Fdfa$)mW=8rsjnGAT=vwIr1Zv5EaMk9|8~(U*IRHxQBbC;Fl7P=c&(z zN?}VuYnm951mzIk9jE4zL?-!3pCck67UK>lA*LFmUy73=ndu@$BI~o~d0ixW;oyQC z_mY(JnmlcZ6H}inX@VY`$|gjbibCgE5(U8{>txxk(=4lq!Bu-GJJdvy#w|Nm^YHH zPm)NJ?5kfOUd0$-mj#Jed>#cSv(@D|#h<=&i94L^?@)Jr=`$bl9gbE{RChR9-L39$ zw0b%Xq5L*N0Yb8I;|Aix+;O*)BEaGTlq16XVR2b$ayeBF3BB>!-I1N?>@-|0ogLC$ z47eq&3(W+vG==qatXEG(;{FtT33Cj2JD?45gatn5N`kbfq#s=-AasmU00rHd7Ag@l z&R3cC*!FMTy6x$?lFJy~lk-n!Ix|6rPB2vvG+fA*riqcyFnsR*Ab5}i|7pVmc$AhMX`w@K84raZ#*;KnKkPMTI@td4Gwn^Op& zOE!O>W_&ZtowiA8Eo+hy=MFPRyD&5%Dv)r>8tra5yoTel_4H^C@7zE^s1NsemegtEadvisr@19LWq-~3mceel~YYtCCoosp{77pWiff7hI2J#`{c7j;m6 zdKNL;usI{a!%Uwz3j0CfVOp=cQQ}|K5{@LtVe7GdYkXwGamsuYN3P?IDsNELX91&^ zRf1onwBfnwvRl*)o&FAPGCvH4Z}VC67>V_5VniF>rK9O*RedCFLo5wGLQtlO+hViX zpu|#cT4=$fx8(^9Hoq;$r$lAF5hppTi4$+Dnxui2vnqwro7?h9`EoZQrjiug38(6) z(a3{GX14yV>Sm$}g$hb|{;eVyG`{3=2@6$1Qn`e!Dxq3PLOz=FeWFs#4U9pJe4!F5 zhZLa_KU^!}dA|fJrd$b8aRNT!_qs|c-};)0i`abIu9dJ)C6tG^L?x6*O8(C(<+^eJ zZ`dGv5??N1uS#fIE}>wfERj(zVYy03E0=I^f zp*Y%}*T{KY)rqt2eQokUqO&D|<7T;{^fQXm%Y4=72hq3#AB{by9crsr6Bqra$hgA>S!))=udc^~F^bC8Mje1j5&!Bm% zvrgS1`*A;4P=_J^=N)+uY-`EvgwC%Lg`M~YTv~VLQ`u|cU}rwF(p*^=zBfu!eul-k zdE%KHfs>_T=8e1wF8gl8io{aEyU3?(b;(ngIqLF(x`cP-11m42`pE1e_ZWV#EmAz! zmERbz63_xA;D`20ig_PvmBodwye}M89lP=QxSZ|ApJH>xz?-ll_nKI66Tc6asP6m@ zq}|t@zlKYA500Ic;*K7CGA`$O@F#J(w%?Uyc~KbzZsp}btlirUO;%4J7@wfxxFg8r7cOOTi&`~ zt(xlG1uxp?)>HSp_)ny-@8%itQjM5G__6}`lz9Gbeh3-v=!XUDrQ*4MJXuA)TUrfj za#25C16khb$G77$zCR~JV10kS6~PFP4&a`wfPNy0@5Pa^U*uLEaDs!;}YM$gLhF?uwS{e%Dyv6jz>>!s1Yk=!*x_SOiK!2Z7!#2a@_2(YupR%__pGSDj`fDwcr7&pq zO$^*YYFM^4QI(19d5>VRu}ExrguhWmNs=)53&@`u*wXdX5S|^sP1cFYuoPynWSqeK z&LMngRh0q**GjRw!}Ij*qRUXpbwh`P$PDq~Q2qwu-5L+W+UP=Y@NrJtdB+apIe6MS zOg?=yjGw#~4A{{&Cc@mzCVCF#mBaA)W__VBYsbd5`^357Jc?}-KMv~cevFSmSNY>Svx4-kqTS=L4K3LGUp>yBVUEHr$}kprPx3-OdW^XABu}EN>nQ|Q z;ClU2d?j7Ke~Qnh>y)Q?6}m2Y8df*1C!WTsRk(&f!{;T9hI@df7ns+$?d{-VuB(l2 z-p-~Yefu-K1@ifSeTILj1r{nXoyJ1t#eANTU`Dt%gYC!LngsKT$ST0_y&}36@OjX% z7YcYsT(U>X%LrT&Ev-j~n#!xS@$F<2!jckeRL*_s@^x?l+Vg2b_LC|=n$PC;E-yucL6-Se{zG)FxFb5~Z#m%d=Xj?F8lcoVYk>kHJn= z%AxEi=l2>96GP7;$aec!o{hGyj^($YOE-^(Css<>F=XtBr zr@hbfF^a-`1w|x6w~4!7;9bj=+W!L2sahiIC?5ut@*ZwS)HW7e+CeBUdeR#ok(P0vw>bRsDEEdNn@xE4_<|%UGAif=l)xrus;;B-^ zxA^p58GUj3ujz}CQ~6HzZ_&Zv{on$4-QZrwT!o2Zalqiyk#_GiejC!ZP2)F~ro~N% zS$10FP3KP_%gO0{nFTcbOqd@RiCHfr>5Ql>cy9D}BzUn50yUwZ6}JoC6Di9D#i)pP z1^4j6BC$g7YIwBI;Hhk#NT0#$X1_q__l8NePhYx%$meZ27RvM)GKI?}ING~tF2Qk0 z2;*k(c1;87*5(^Q;=%;Yhw^nDh+Z;A-d-LLSwDtMhei(e0(etH&9hhSbXi)Yj*SEtlV z@RLH)>pzO~vk(+Cw@A{?S0Z*cPu6D?htT79{#?A}?2@>34xeRV-XfPj=I}mc-wk|? zue(*^1mY^#%Yx%F?EwdsepC&V4}kaztT}#>Nf3$|%83r~&|H|>JH_vFc~{r!tpq5k zD;z^qZzFHvm@D4^xq~*Af@N{b!Hv9zNScpH#01fOK5w6{P=h5f3<=b|ew+Z(FVnAw z(csgc{?cgi_I#cmcZJ^5f|+^L#&L0ZK0;W^zplN2d(E*4&VAbg?yFN_ljMsEEHpw) zO&Tq>F8~)_7AF^A?r~mt7Q(bUFRok2>vvS`Vb3tUm^gSx0ZryM#htqu^&}0HS#h>N zFhA!v5YWyZ)lwk$Z=)@1?21y6ng(q-O zNLw~bcvtXSQcR14Mpj0A08TPaDNL_O7%MQ7{6PG%0`hH!NLtC0n z{c|yLEl;nuvj9~?DtNfXhj@?zcgI>(TOz(%%bTQ~k}Qci4z`_>YaW{m^%4RKqLvm{ zF^)6Db?fB(=I(X8R;CivaM_pfNaCZ{e?OL>)%hUwG0v{Vn(K>%j>5dw6OkH8}}#YFriT_wX$1 zV_5j?khK6r5*-1^@c8_(2X3RQqWWHbciKTUXZci;rSvgdGsIWUOM0UzC8mb$tNcM( z%DQ5H5I^nZjj#Wf($w@{|32vcs#OLAY-uQSp4J=_Yil;@<-QH#;e9+cO3fq>aULn= zT|!9m>V5E$ofcL1V_N#97_*-jm5Q!z2VeoO755(CUD#@|<^X1~?~2_Acx_&=Ra`m% zjkH?SItXR^uIPIZO6Zb!>L70h!d!Kb_b&Bukxi`vmT(7qLh42LAu!0NqU#~d?mnYS zOL6)TkBwAQ(#IJ$hAgNeDu2N1!pYM51D@zOVp0EHAMgukr0a*g9XsWp^dTz3zaAme9G?#5EPNK zT2WL4WsgSTFL8GI*o@J@a+~WSD!0&u<>#Dqn$ z3)l;viL+lo_kJd#PGKG7q`2-BMC>Us{uF-%Pgm*b3(@X0f6V)x370LP+t0}g2{ekb z(~pTyPs3#SR@lFUik~Z*eaSn)=p6ASCirv2yf2|x=ZLqz7wEcF$cD+?p#gWv%sXmq6J zMi>R6SIi8>G?tVDNagxeD((TUZBDW8E8dikE)?1sK9bE7W6toO=&|5yzL5PWYMtfX z!4{95g^~V~Sa231=4Wy7EKf)|L*RPCd0YCUPXyxwu{{8sT_0H}+~1(nXGHEdyk_KO z%80KseI@?&4e#9HYkH&@EjpAlT>qXP$uCS_hu38yz@np2HB9F4la8-3QXWM5*xk)i~KGPOGMkhL&3S? z3|%gZ*zb8tr4tfew4Ir^_2Hwc`drcad!AYUvZM=&l>(Q@$T(V-017K8FdYSw%NGDW zco^m#C&bb3c~gGcq?m*%XA>1Ts-I;@=M3f18sgF~(vy$DCk#i6 zJi_`9mCh*Lp@<&1sB zqG_Vqul(Mm>6F!pAf5xx;R~CJo;4#0DKHER5iGRNaqpkqNK0XX%`<8f!Zz`4V=divO6GBs ztlTMnX{>pw|Ek{DeG_)e;^qX#jbfO@^@(~(n9^w zCfe8kqAHR|!iM=) zvpjNh4%)6C687sfPvWJ}M1&mvfp|{vee3JA5stlTD2x3&uhUwy(nm*I?GnDOD}K6O z^Wu`yUb~4M@(*gS^^R~X+-HLKZWpbWqj;~07*AI%DgUtfq)=hbO_=ZXrIg8+0^FRs zMvi4NM>cXCR`rK`G87Z`3#yJB!2acoseiS`@=(d9Ta{`0TF7v_-8d83 zMBsXb{$l^*H)#ubAG3=8jJ6;OzO1OAv&wkgPT6N}Fs94u8B)hCPpRUa=#n;GD$}a0GW~d-`Sy*un|JXQ*lT6|{3#`xX zf_OV+J^gkUw8dtHinm1QQ|G(2#tuus`A6TaHD`{?s^A6x&VG>2p2Cn(4jTSxW`aL= zfc6}3rUV$GUKe8Zo#}zc-MsXX#GBoBPAe5}=kC$m*-E?xiUuB4(Nf_ji((s?5`O-6 z1GP+ED(;@RPwUO1O+r@uDt>-cGyH=d&^}@ifWAB_2M6Y9^&kf)-L7k{S zqiC8M6^(i(HZ;Z_OJZV7j3qH9Ka*IZF)^|HXW#cwY~Ppv_dFlZbL=~_ZFY8dW_EVp zeb3ml#&JK^*P{d^}zd75^Uu;1g*Y=u|F(%jfuuq6Hrip)U%*+Ak3L ziiGw4ZNgWSnCIxhr%HmoP~sEzy;#}*JNq2EUOk8Vw$@Lt=Fw0{-Sx$h(MWF0-#H;tM!!=mZz@ z2%qDUELxeBpOarUOPy_s{O?WRA0RW|T@ zRD$jUp2Kx_HK~6LXK&_fovU5hB^ z^wV?pi~nWBtBSfvul$t1(X6-c+s50<(fMdQuS*wd1?=E=Xy9G>n3fG{Py;jUTpos8 zVkeDXt;|6unai|xr1Smy%XaY*FJr#tF+zX!nKbgS+U4>B19#CV{}i)#@!hn$9kQF} zP}S>pb1MaYd-zB~&fLQ@3A(2RBlhwp%#O6l)S-@}h6?MV{TtO`T9cO7H}~>^L~-YR zd@2RU_VJcN`tGsrS3DZqm609tCDuK`z3eN>_%3>7!ohYNdVqfxc<`A{@F`!3e6vz5 zKAcwNW%i^`Xl^&XtId3ec9I^RL=S}wSbA`nVvA2XrsevBypC?V1G1ky$US-gXVu>m z!T*BXm-kUWH>e#|z9f%5^avlz=o5o$kCMVYjfal%KnkvFf#)$E=y6(hiH~%QkfO!5 z$9VPf+41UP(RPe)CayI&PO|MwJ2B7X5My5UHJ_1aBjo*)JeSE1J-bC%~h`UOkR@=4{-abNOCpM8>M`Hlsdggnlv za{%4h?Wy1G=+r6YX(?YM4*Kdz2jjM{xR-MJ6lQ)#8yuIu;)BCfp({&rbkQDd*a*EQ zjXlX-q;f_B*GZ3hz*+8vdFS{;l8}m0 zeDqG0DS`Yer_l%7+C+=Gvi|ZvNslbA{7=Gl&}<0_PNI~AulrGQ&%MJ;eMDs;|pJ0<|Tt;wIOlWL$RCr``OmtLqL_}zGw2uu- z3q?}p7=yu>la*<)8d66YQ^#QILP1wljZ-7NV+{sFR%W)*??nszaJKMv5BxtQv{gvR>f4*m z6zdpkfJbMGEWG!nFsmid{TPI}qcnO4kIfc+@XxuTpVD}(?6M$i-UWOS=ZV2ell%&u z=-msvu=`4I#j^RruePH*BV)p2B0?h~W5dG2LSsUr!$O-DRLCTIo-T^KG+)@1=3_DC zxENILl{tfQwdNaCepaEzM;Kx|WM*V#nvG_wemX5c(y-+|2J8m?e`Yu ze_N|(#E6u1Q^@}!eO5}kB{cJ2mfzGhEc;(3tzRkDYDq~o8d7c6@MgvN)fyJ-s{)HC z&a2j_*uR=5i=?MW7KJ4npe~Cp9$Ph##b9X>ZNSF{;Fcm$2X7S#p|q^00=g0D=Jf1Q zt(vNkEI?_kC3BW7XwwvX?}MgF+Xgc9b(1v*QO)g|;+c4;gQW|Fr_#Q;23%Mu^h(@Q zz}3}aOfi`6$by83T@q zy14m*aI@#UA>0`2jJKDFC}*2FeMD{-+*d53T)IX{h9%;el{EkKONBf7Ef$qoHw<1Z z#w*=hAbUg5*A*6vN=lDrDo`cbXilTaHzF}c&6;g^lDRsBn~md>uVsyF)`s`VKL#cAz_gbu@N!R zA%maJL4VYjib}Y7o2c#el9aFE{<~rvdtG+H2we4t&?!UAxTrHUej>VKu*WY#Cwdr$ zZpYBIVhtKfNMsCgB}uS)g@{!Q+bW<>{fFoqX0NhRL^361SB02cl_*2PD$!0EUNOUo zgF=T>S5rBuPg5^vX-GxVnLah$R^>#GLpxW?U-^$G{e>VMWHff#}l9pQYY$g^#RdY z$=hB59de)0gFg|SSU#3~BFZr6fEa_+fjt?$t@Vi*jWa(L1Nb!hz^wz z5E7CQl8_J|9%@)%zkNc`SEGtB_@wB>7m{n55>;k6^OOkUi)b62zsU=xFfrv*J2mCTp23GNr=Kr(N4M6%_L)Ux`j$p;581p)}{i zLqbAhVk1K%!eUpT?>TXiwo)IQ6Svt)yz#Y=&i9Mxe@?8zpU%@fS|yW|;L&eH8`stB zO_H;6ELK@Z+MwCD;!oESw)#2TXXy2v2ynwIkNHlxGWWIYoTC{bq_4pT7kn!` z@x6;;$;-%l+urMv7|HlMY@{}*ZzYmAxOQ2za(kEMsgz`=;B9ZHfmxTxWc=(371sX> z>3i?9G`LGALKStGlS5`IuD>EeWn>>lpT)W&mb$+3O{7A7Ii|aYSrnbC^0hm)Zqj`yWLr z+hl+HN8!w9MSKy5T^FS|*AI=ubmSlyhDrEe<`cFA%NTIfDBbZZ*%ED2QtY{jJqk0 z&`t^kTXFX-QQiGxx(8oVG3of|j)=wTw?!&>Ho9VP97N+IU#LnxmQUo2Y=}2*i?{e5 zX2j3~qA~j4A%%J1cM-<-Qm>Sw+R?j)hC5<0c~9=%5yy#1Wp_!L9$*zz#3w3X<_GBz z6Z=!N#D4e0dRqQJz9-J}L$o3%u*vpbMA{g-2abA;m6B!tFmO>17h$A{O5tVLSGp8?xEPvPTE&J5-k`zWk2;; zgh>wwg}+MytHTEjV(4taTpZ>Me_;y_)%fS-P#Xc&*cbNR0)SB+L!IHDN*zYo%wwcr zh%tO=Ck%D-;+o#UvE{=b1zH!1Id5V z?>@M4GLXI02|V#bFOY}R9(utrIs`h@8|Lt9B+e4ML1$b$k6amgKXCSXNwq)OhgOGi zjQ>pC_fZUtk*0wM_N)m*`G4sPU!^tS?U#{oofOiRnvjYoYe5aSU)XusTxs&%?gO>( zeqEA@0Y0#X|4MT6R0{axRUhcdf1^QKQk%wfMs4Ew4ScIMOy@V*G+bR9A~3lQ^y0T@ z*{MR8>%a(pnFCuee@MoO{`7oLK2M?M z1;83S5lC92W<7WZH`OQn1GW^KZxVNL@?P~PYNiw@*y5x};8PWPUBOY(MrE2}Z?;LyWbV_I_Z>3Yi28M;>#uDVcAgTfrge=}SLyTV z+^fv$8l^2E3Kh)!P)t=8DCpAw#_=L0{fRN@jQU*C^WI_LLvJZPYyd6!BIR8Lmo*#1>u#tF&=OfRbrLWMTHu(*;7V*f*%-#~Wy%Qj3x@hQtO>;P<;rM0P5CN^ zr<=#6n9|b>^xtM&f%>Me^$aV5rRA$2!5#ov%~6X~^}#;KtW0>z`=02`=%WgAE&$nNlfEcaHC7_&_=M zBsLTiLtrG^Xx|0*`8Fa|RWc)^p(Yu9JtAR_+YZIC6pz8)Z_${V5=njO5d|F;yMvcIRbKLs zlrc|qjyraah8Ws0%Z~;N-=*YWMl~3MePWVGQ%&hlfp$cg# z|CZ1XJzCOpnS3_#{ZuSjUcK;KOQ_8cC?mDxomxS4{)uAJ;x@N}I&{qGL@N>`pW?$- z)Tf8Ab8Beja#)FwqX&Dprg6BgHR$O&mXBM*5ICx&8T#Osw$KPOmq9Cb4D;GR5<8A3 z+rR+QM-AG-G}1@w+7gi`6~ql~VS#(>cuTs~NLIb&6kcfutw}TawTEQd6VGW6Dg1L{ zAlZe1__#eZCv&BF9P}bRG&v4lBY}E84hFk_MNE?fB}?~*tlSt6>9jR3U0(ILTdc(FIWphnZ+5k4b{&{5DBRwH_kQ8OxS~52sVL8tS(s;{6?b zkVKZpU1|gyNR4>Vn-r~~Cjk7RbRgc|SOHD3xF-pPdrHVtlJRs;vN8Tt8fngw2R*@E z`Afk~nULr@BEuS=Vz^J2$E$9aiM=T0-^x=bjGN1Fa-aUAG*jcNlE=|tNi!MJH2=$k z=kX4|%tMmsYIz~Op(^Pd8d-rjqBm6LkExBSm!`NkN#}g}P(XQ$D=P&YFZ70)d@4`I zpp}rUOygMA2O7Ff=aR+a60;*QE|s{P-w&!{?YGJ{jQX=oLSnXAlj{TeL8>x`ri8gb3~gFBzc zn@FgRJ~TAdX0ed-(2$mHna}H~Sl0*x$#_d4cZ`%Z{Yg+P;ElC@`ldg4x)gEM{ex9{ zk$cO10Mz0Od6-s#99P(F0I_EgZ>eQbQ9b6nbs6=05zyTz5i}CRQ=%icB`>FF6 zuUr!iaP53j^m7N^T!(!dCGjm)-^gLo+?{}h3EV<7104Y@xDlGuNf_Vd-jLqpXA zO@qLT?3;o?kV^aFaVjrW0$q}!uFDGE_8IQVS9(V$Lpoo{JD@3<=J(xXXh0gwXD}gG z@kO}tHPSMN216gdnlI3x*It93bVz^0YcSVs4NsH>4$2<2bP&lx!|TwHujNHDVMjdi zI`rUg^Ve~6CM4jnAy9*~Sk>ZFXnl|8>CaZ_*ULa4%m9=KagA{)GEMgsZgJP$X`Rh z;ndKRnKUvdrBTPvNTo`*@uAP-e@G>Hw4L{U7VoPYVX|e9+VMQTo?_>k`(-Eookslq z2)h}{`1_cT!5&63G(ARuH{Zog8Z>bPDXQI6DaG}#6seXOA8Xk21f!}H!tCY4)ik(a zBzTY}{AeV^DrH=?SCsv9{dQ9r;&y;XYaAGip+(fV3!_NxeuBS^f&$Windvag?Nk0L zd+8AN8V$w#FpZAZW-?rhMpIue%mg=Qs{RNse1Zk+Zld_6W||d8`MhWG-S{y+Q=;PV zya_sy#tg`SS^R`nYCh^SNMU`3EzMBGPjVZ!G1FL8r_>*2=%G8sN68i>V?kXqr7|<2 z4Jpx$nb1#vhF3~W8Je)%_+b|H>RCQmGimCMg=#KeYU2SH zjwLrkpRo`MU-7;qYK|3%-uxWzf{)J$e+;P)0w0g1vGO%<_bjR^zHR}xyz|^y?S*mQ za6z@Feak_GFK{pobI6e-3pb7z`BspHeG@EU2x>{Ls77Hu0DtSQ4yu0}(;IooKlevdaS;M4F2uCGv} zO377TxdKYk_#*~qL!;W)c#ZT-gVAE4vb6tcmdq?e=6Lf@n3WARYyQl=vNBC31JPi~ z98X(dCKFX@{4cJ~rh$2#`{LxOq87*;^rg~l@TF7be`M2@tG{7&EBMvA!K=xZXNA$; z8*RYZVhkfJM&nK15Km8~o9|`5TWGa{e4%NT6?{E@=dP;jQ~tYyN3Bq&@?Gw#(jfo+ z!2@v5G}`&U#~a{p(?kPWa<#ERQ~no;^L94y)w<$77TO?)|4sEsWXEjVaLKdb#(#Ka zRlpjEn`DTQsC~ezP?%}6mZRwLp^77hl;h~}5&DgTK>nCiOVJG44$T*sJ`U;L?sQEo}FT~=FSyzl+e(cCm_w1k&!!J2+7Do;bx%S z2a|y|UdAjCl?)m&w8NTiEfUTd#*EaAtcAi|;w`D8EM}W&kO-0>Yq6vJCBoHUFs0HKg*khv&`I*;SVGxK?a^R)Q_W;DnwJR|!kVe7$g&u-)Q+a{@%Uen5L7 zB$^Vl<=WZ@k4zxDaHEKPQWVyjNdBcw7&H;0b(_UtRTATsFtUnhQQ8zYPbBlFRA7~+ zv@+PTbP{=Aw$cegT4#QUWs|_D+a{8qm3_^eTphC!j&=ln3#+cM7SGkMYD5h^MoPmGhvpZnx;B zE-SF0DXGB;dC-LK5&dQImUuaj)?|BSLH$VS^v)-RR)!_{wF@!&rMNM9@mB*Hq~1a#X0>X&Cn& zEom~Rl6`)3DiM54L~HGNI2HUzi`1D$6r8}Z(@3X$hC8Q`TkYi1>5$-dN_5n^g)Fvd zXwAA6!8SjIgXFwxbie=6qN8VYy zH3K3%(u~7xBjrS~rCPs~sP=S|LFi2K@two;ndEl*no6yb2O;?DOtRBA>PvLy4>NpUa_368f)bt(j=G=RqyE z>l(d-vSYAh0MWE?A+3jo&LbbsFPJ+ITD$xz)SXtWB0K;5JhBvj!`t&9%;kpYE;+CD zv+9f_8@hqZO+gFoq;X60$zw_*wK2a%{G_25hjj|UO?O+UJF0`U1sNHA0`*0tuzMB~ zX}^np=r;|z&>0Rg#P8sqLYi53@$W(!mwyoZ3jRaAdJm^8AQt^ejaSXka|>ve{)N9R zfY$szb;#ajWQMC{_AVl}|1DZcR4ccCL`zASY^iEXouF9u052C&9S_lWAc=A+EzEq9)#54BZsBHfk(wSqRst zguWf(dL_80N>mM8x&(T#N_hEA8dwjOkiFA~IF}=3`U$1=sU*}BNV5dlD560>Il{{+`G{%@KHn`f0wvsg$=*aEn!<@X+ z=@^H_8Z^RYP7P8wxaA1~Rv-O#K%*uNsFKF+bk@mJ9_t8_M|Xl`amyhx zL#M`Q-T^gQH+Au1?G;dqY2-}G!<3TSh|#C+nUj!v^h{x_nOZp^=|q*YHuo53tU2!A z0lw{nTs)Ml2JM-UwKQ-FcBgY#pn5bJZ3VP&5bj=uIk`S1)|s&o_<|Er-l*G7k}<#z z88u(gG9N6o!>@D%BV`j*!@`}=r{>&7o~h-hS%TyND)y77lT!;P4BZXU_TY~IT$Esq s$GUPJ+r!JiCd3)eY8TUIaP6NSgbmEy=P6b8tV6VXWl{F|N6BmRKfM(j$^ZZW delta 54940 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 rV7?3QF(0+)bJ)yW*cwOrtc$ list; + // Whether the trace is cut short: an export call has no answer, e.g. the run trapped during + // it. Its last lines that the host records once it sees a copy complete may be missing, e.g. + // the guest's drop of a stream, or the value of a future it wrote. + trace-cut: func() -> bool; } // Async, so that it can await the main component's async exports, and run the jobs of diff --git a/components/recorder/src/lib.rs b/components/recorder/src/lib.rs index 6fd11cb..a471482 100644 --- a/components/recorder/src/lib.rs +++ b/components/recorder/src/lib.rs @@ -76,6 +76,9 @@ impl bindings::exports::proxy::recorder::replay::Guest for Component { fn finish() { with_replay(|replay| replay.finish()); } + fn trace_cut() -> bool { + with_replay(|replay| replay.is_cut()) + } fn check(line: Line) -> Vec { println!("{line}"); // A replayed `wasi:cli/exit` ends the replay with the guest's status. diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 49c57b0..7f12fc7 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -206,18 +206,38 @@ pub struct Replay { lines: Vec, done: Vec, renaming: Renaming, + /// Whether the trace is cut short (see `is_cut`). + cut: bool, } impl Replay { pub fn new(trace: Trace) -> Self { let done = vec![false; trace.0.len()]; let renaming = Renaming::new(&trace.0); + let answered = |id: TaskId| { + trace.0.iter().any(|line| { + matches!(&line.event, Event::Return(ret) if ret.id == id) + || matches!(&line.event, Event::Cancelled(task) if *task == id) + }) + }; + let cut = trace.0.iter().any(|line| match &line.event { + Event::Call(call) => call.id.table == Table::Exports && !answered(call.id), + _ => false, + }); Replay { lines: trace.0, done, renaming, + cut, } } + /// Whether the trace is cut short: an export call has no answer, e.g. the run trapped during + /// it. The host records some copies once it sees them complete, after the guest went on, so + /// a cut trace may lack the last of those: the guest's drops of streams and futures, and the + /// values of the futures it wrote. `check` accepts a guest's line about one that it lacks. + pub fn is_cut(&self) -> bool { + self.cut + } fn pending(&self) -> impl Iterator { self.lines .iter() @@ -301,6 +321,12 @@ impl Replay { (None, None) => line.by == Party::Guest, }); let Some((idx, expected)) = found else { + let late = |label: &Label| { + ["#stream-", "#future-"].iter().any(|kind| label.name.starts_with(kind)) + }; + if self.cut && handle.is_some_and(late) { + return Vec::new(); + } panic!("the guest did `{actual}`, which the trace doesn't have"); }; let expected = expected.clone(); @@ -576,6 +602,31 @@ mod tests { replay.check(&line(Party::Guest, ret(t2, Some("#session-2")))); } + #[test] + fn cut_traces_lack_the_guests_last_copies() { + let t1 = task(Table::Exports, 1); + let stream = Label { + table: Table::Imports, + name: "#stream-3".to_string(), + }; + let drop = line(Party::Guest, Event::Drop(stream)); + // The run trapped during `t1`, before the host saw the guest's drop of its stream. + let mut replay = Replay::new(Trace(vec![line(Party::Host, call(t1, "run", &[]))])); + assert!(replay.is_cut()); + replay.next_host_line(); + assert_eq!(replay.check(&drop), vec![]); + // A whole trace has every line. + let whole = Trace(vec![ + line(Party::Host, call(t1, "run", &[])), + line(Party::Guest, ret(t1, None)), + ]); + let mut replay = Replay::new(whole); + assert!(!replay.is_cut()); + replay.next_host_line(); + let check = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| replay.check(&drop))); + assert!(check.is_err()); + } + #[test] fn started_then_return() { let c1 = task(Table::Imports, 1); diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 28e6106..195f7d4 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -103,7 +103,10 @@ variant event { write(chunk), // A copy: the host read the items, which the guest wrote. read(chunk), - // A future's value was taken by its reader; `by` is the writer. + // A future's value, and who wrote it (`by`). If the host wrote it, the line is recorded when + // the host writes, before the guest can read it; if the guest then drops its end without + // reading, a `drop` by the guest follows. If the guest wrote it, the line is recorded when + // the host reads it. value(future-value), // A stream or future end, or a resource, was dropped; `by` is who dropped it. drop(label), @@ -157,6 +160,11 @@ What the types don't enforce: lazy: if the guest never reads a stream the host writes, no `Write` appears. For `stream` a line is the chunk one rendezvous copied, so replay can give the guest's reads the same chunks. This holds for pumps that don't read ahead; [Read-ahead](#read-ahead-in-pumps) would change it. + The host's side waits until the other side is ready, with a zero-length write or read, which + completes once the other side reads or writes and leaves its copy pending, so that the copy + completes as the host issues it, and its line is recorded before the guest goes on. A future + has no such wait, so the host's value is recorded as it is written. Replay's jobs wait alike, + so that the same events wake the executor. - **Order.** The trace is not a total order. Each line belongs to a task, by its `TaskId`, or to a handle, by its `Label`, and the lines of different tasks or handles may come in any order. What orders them is each task's own sequence, `Call`, `Started`, `Return`; the `at` of the host lines @@ -482,6 +490,12 @@ The proxy puts a hop, its own task, between the host and the guest. With deliver | a guest read or write that completes at once, as the host had data or was reading | only if the pump [reads ahead](#read-ahead-in-pumps); otherwise it blocks first | | a host result arriving before the guest's next wait | may arrive after it instead, as the executor needs a turn to pass it on: a slower host, which is legal. [Read-ahead](#read-ahead-in-pumps) doesn't help: an async import's result has nothing to read ahead, and a stream's data that arrives just before the wait still waits for the pump. A wit-bindgen guest, whose callback gets one event per wakeup, can't tell the difference when this is the only event, but can when another is pending at the same wait: without the proxy, the lowest waitable of the two comes first, with it, the other one. A guest that calls `waitable-set.poll` also finds nothing pending | +A run that traps, or is killed, during an export call cuts its trace short: + +| | | +|---|---| +| the guest's last copies before a trap: its drop of a stream or future, or the value of a future it wrote | may be missing, as the host records them once it sees them complete, after the guest went on, and the guest may trap first. A trace with an export call that has no answer is cut: replay accepts a guest's line about a stream or future that the trace lacks, without checking it, and takes what the guest writes since, so that it goes on to the same trap. The host's own copies, and the guest's writes to its streams, are recorded as the host completes them, so a cut trace has every value and item the guest got, and every item the host took. A trap after every export call returned, e.g. in a task the guest spawned, leaves a trace that looks whole, whose replay then reports the missing line as a divergence | + Whatever schedule happens is recorded completely. Not every timing the unproxied component could have is reachable while recording, as the proxy only makes the host look slower, or, with read-ahead, eagerer. Covering other schedules is exploration, which fuzz mode does. @@ -591,10 +605,12 @@ executor runs both. or a handle, e.g. the `Return` of a STARTED call, which its mock waits for; `check`, which checks a guest line, up to renaming, and returns the host lines that answer it at once, e.g. an import's `Return`; `pair`, which pairs a label of a stream or future replay makes; `finish`, - which checks that replay reached every line; and, in both, `fail`, which reports a panic of a - generated component, which has no stderr of its own to print it. Waiting for a step is the - executor's, not the recorder's. The bundled recorder (`assets/recorder*.wasm`) and the - host recorder of `run` (`--use-host-recorder`) implement it, with `Replay` of `crates/trace`. + which checks that replay reached every line; `trace-cut`, whether the trace ends during an + export call, e.g. in a trap, whose last guest copies may be missing; and, in both, `fail`, + which reports a panic of a generated component, which has no stderr of its own to print it. + Waiting for a step is the executor's, not the recorder's. The bundled recorder + (`assets/recorder*.wasm`) and the host recorder of `run` (`--use-host-recorder`) implement it, + with `Replay` of `crates/trace`. ## Correctness @@ -649,8 +665,8 @@ shim](#portability) could provide; see No guest task on import calls, in as guest-internal streams, or what the guest's waits returned, which the shim could record. - **A fixpoint test**: record a run, replay it while recording, and require the two traces to be equal. The spike does this, for its scenarios. -- **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2 and p3 fixtures, - `wasi_http_p3`, and the spike's guest, and replays each trace, which has to end as the fuzzing +- **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2, p3 and p3-record + fixtures, `wasi_http_p3`, and the spike's guest, and replays each trace, which has to end as the fuzzing run did: to the end, or in the guest's trap, e.g. a panic on an error the fuzzed host returned, which is what fuzzing finds. A trap in a generated component or the recorder, e.g. a divergence, fails it (`tests/run-wasm.sh`). diff --git a/src/run.rs b/src/run.rs index f6da5f4..9a21132 100644 --- a/src/run.rs +++ b/src/run.rs @@ -80,6 +80,9 @@ impl bindings::proxy::recorder::replay::Host for State { fn finish(&mut self) { self.replay.as_ref().unwrap().finish(); } + fn trace_cut(&mut self) -> bool { + self.replay.as_ref().unwrap().is_cut() + } fn check(&mut self, line: Line) -> Vec { let line: trace::Line = trace::convert(line); println!("{line}"); diff --git a/src/traits/fuzz.rs b/src/traits/fuzz.rs index 477eb1c..eccfdc5 100644 --- a/src/traits/fuzz.rs +++ b/src/traits/fuzz.rs @@ -352,8 +352,10 @@ impl FuzzTrait { where T: Fuzz + ToValue + ValueTyped + wit_stream::StreamPayload, { - // Writes fuzzed chunks, then ends the stream. A line is a chunk, which the - // guest may read in several copies. + // Writes fuzzed chunks, then ends the stream. Each write waits until the guest + // reads, with a zero-length write, so that it completes in this run, and its + // line, the items the guest took, is recorded before the guest goes on, e.g. + // traps; a chunk the guest takes in parts is a line per part. fn fuzz(u: &mut Unstructured<'_>) -> Result { let (mut writer, reader, name) = host_stream::(true); let label = Label { table: TRACE_TABLE, name }; @@ -361,24 +363,34 @@ impl FuzzTrait { spawn(async move { let mut chunks = chunks.into_iter().filter(|chunk| !chunk.is_empty()); while let Some(chunk) = chunks.next() { - // Each item's, for the part the guest read, if it drops the stream - // first. let waves: Vec<_> = chunk .iter() .map(|item| wasm_wave::to_string(&item.to_value()).unwrap()) .collect(); - let at = trace_at(); - let rest = writer.write_all(chunk).await; - let written = waves.len() - rest.len(); - if written > 0 { - let items = format!("[{}]", waves[..written].join(", ")); - let chunk = Chunk { stream: label.clone(), items }; - proxy::recorder::record::record(&host_line_at(at, Event::Write(chunk))); - } - if !rest.is_empty() { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); - host_dropping(move || drop((rest, chunks.collect::>()))); - return; + let (mut items, mut offset) = (chunk, 0); + while !items.is_empty() { + let (ready, _) = writer.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((items, chunks.collect::>()))); + return; + } + let (at, count) = (trace_at(), items.len()); + let (result, rest) = writer.write(items).await; + let rest = rest.into_vec(); + let written = count - rest.len(); + if written > 0 { + let items = format!("[{}]", waves[offset..offset + written].join(", ")); + let chunk = Chunk { stream: label.clone(), items }; + proxy::recorder::record::record(&host_line_at(at, Event::Write(chunk))); + offset += written; + } + if let wit_bindgen::StreamResult::Dropped = result { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((rest, chunks.collect::>()))); + return; + } + items = rest; } } drop(writer); @@ -399,6 +411,14 @@ impl FuzzTrait { spawn(async move { let mut count = 0; while reads != Some(count) { + // Once the guest writes, with a zero-length read, so that the read + // completes in this run, and its line is recorded before the guest + // goes on. + let (ready, _) = self.read(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + return; + } let at = trace_at(); let (result, items) = self.read(Vec::with_capacity(capacity)).await; // The guest may write its last items as it drops the stream. @@ -428,23 +448,20 @@ impl FuzzTrait { where T: Fuzz + ToValue + ValueTyped + wit_future::FuturePayload, { - // Writes a fuzzed value. + // Writes a fuzzed value, whose line is recorded as it is written, before the guest + // can take it and go on, e.g. trap; a guest's drop of the future follows it if + // the guest drops its end instead. fn fuzz(u: &mut Unstructured<'_>) -> Result { let value = T::fuzz(u)?; let (writer, reader, name) = host_future::(true); let label = Label { table: TRACE_TABLE, name }; let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { - let at = trace_at(); - match writer.write(value).await { - Ok(()) => { - let value = FutureValue { future: label, value: wave }; - proxy::recorder::record::record(&host_line_at(at, Event::Value(value))); - } - Err(unwritten) => { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); - host_dropping(move || drop(unwritten)); - } + let line = FutureValue { future: label.clone(), value: wave }; + proxy::recorder::record::record(&host_line(Event::Value(line))); + if let Err(unwritten) = writer.write(value).await { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); } }); Ok(reader) diff --git a/src/traits/proxy.rs b/src/traits/proxy.rs index 0c60ab3..7a02511 100644 --- a/src/traits/proxy.rs +++ b/src/traits/proxy.rs @@ -412,10 +412,14 @@ fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { } } /// Copies the items of a stream from `from`, which `sender` writes, to `to`, which - /// the other party reads: once the reader reads, so that the writer is read only - /// then, as it would be without the proxy. A zero-length write waits for that, as - /// it completes once the reader reads. A host's end may answer it at once, without - /// being ready, e.g. wasmtime's, which then takes a chunk of the guest's early. + /// the other party reads, once both are ready, so that each copy completes in the + /// run it's issued in, and its line is recorded before either party goes on, e.g. + /// the guest traps: + /// + /// - A zero-length write waits until the reader reads, so that the writer is read + /// only then, as without the proxy. A host's end may answer it at once, without + /// being ready, e.g. wasmtime's, which then takes a chunk of the guest's early. + /// - A zero-length read waits until the writer writes, leaving its write pending. async fn pump_stream( mut from: wit_bindgen::StreamReader, mut to: wit_bindgen::StreamWriter, @@ -427,51 +431,58 @@ fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { { let receiver = trace_receiver(sender); let wave = |item: &dyn ToValue| wasm_wave::to_string(&item.to_value()).unwrap(); + let dropped = |by: Party| { + proxy::recorder::record::record(&pump_line(by, trace_at(), Event::Drop(label.clone()))); + }; loop { - let (result, _) = to.write(Vec::new()).await; - if let wit_bindgen::StreamResult::Dropped = result { - let line = pump_line(receiver, trace_at(), Event::Drop(label)); - return proxy::recorder::record::record(&line); + let (ready, _) = to.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + return dropped(receiver); + } + let (ready, _) = from.read(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + return dropped(sender); } - // The host's side of a copy is its read of the guest's items, or its write of - // its own, in whose step the line is. let read_at = trace_at(); let (result, items) = from.read(Vec::with_capacity(4096)).await; - if items.is_empty() { - if let wit_bindgen::StreamResult::Dropped = result { - let line = pump_line(sender, trace_at(), Event::Drop(label)); - return proxy::recorder::record::record(&line); - } - continue; - } let received: Vec = items.iter().map(|item| wave(item)).collect(); - let items: Vec = items.into_iter().map(ToProxy::to_proxy).collect(); + let mut items: Vec = items.into_iter().map(ToProxy::to_proxy).collect(); let waves = if sent_by_caller(sender) { items.iter().map(|item| wave(item)).collect() } else { received }; - let write_at = trace_at(); - let rest = to.write_all(items).await; - let written = waves.len() - rest.len(); - if written > 0 { - let chunk = Chunk { - stream: label.clone(), - items: format!("[{}]", waves[..written].join(", ")), - }; - let (at, event) = match sender { - Party::Host => (write_at, Event::Write(chunk)), - Party::Guest => (read_at, Event::Read(chunk)), - }; - proxy::recorder::record::record(&pump_line(Party::Host, at, event)); + // The host's read of the guest's items, in its step. + if matches!(sender, Party::Guest) && !items.is_empty() { + let chunk = Chunk { stream: label.clone(), items: format!("[{}]", waves.join(", ")) }; + proxy::recorder::record::record(&pump_line(Party::Host, read_at, Event::Read(chunk))); } - if !rest.is_empty() { - let line = pump_line(receiver, trace_at(), Event::Drop(label)); - return proxy::recorder::record::record(&line); + // The host's write of its items, a line per part the guest takes. + let mut offset = 0; + while !items.is_empty() { + let (write_at, count) = (trace_at(), items.len()); + let (written_result, rest) = to.write(items).await; + let rest = rest.into_vec(); + let written = count - rest.len(); + if matches!(sender, Party::Host) && written > 0 { + let items = format!("[{}]", waves[offset..offset + written].join(", ")); + let chunk = Chunk { stream: label.clone(), items }; + proxy::recorder::record::record(&pump_line(Party::Host, write_at, Event::Write(chunk))); + } + offset += written; + if let wit_bindgen::StreamResult::Dropped = written_result { + return dropped(receiver); + } + items = rest; + if !items.is_empty() { + let (ready, _) = to.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + return dropped(receiver); + } + } } if let wit_bindgen::StreamResult::Dropped = result { - let line = pump_line(sender, trace_at(), Event::Drop(label)); - return proxy::recorder::record::record(&line); + return dropped(sender); } } } @@ -500,7 +511,10 @@ fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { } } /// Passes the value of a future from `from`, which `sender` writes, to `to`, which - /// the other party reads, unless it drops it first. + /// the other party reads, unless it drops it first. The value's line is recorded as + /// the pump writes it, before the reader can take it and go on, e.g. trap; a drop + /// follows if the reader drops its end instead. A future can't wait until the other + /// side reads: the host's read of the guest's value is recorded once it completes. async fn pump_future( from: wit_bindgen::FutureReader, to: wit_bindgen::FutureWriter, @@ -518,12 +532,12 @@ fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { } else { received }; - let at = trace_at(); - let line = match to.write(value).await { - Ok(()) => pump_line(sender, at, Event::Value(FutureValue { future: label, value: wave })), - Err(_) => pump_line(trace_receiver(sender), trace_at(), Event::Drop(label)), - }; - proxy::recorder::record::record(&line); + let value_line = Event::Value(FutureValue { future: label.clone(), value: wave }); + proxy::recorder::record::record(&pump_line(sender, trace_at(), value_line)); + if to.write(value).await.is_err() { + let line = pump_line(trace_receiver(sender), trace_at(), Event::Drop(label)); + proxy::recorder::record::record(&line); + } } }; items.extend(file.items); diff --git a/src/traits/replay.rs b/src/traits/replay.rs index a097503..2da81d5 100644 --- a/src/traits/replay.rs +++ b/src/traits/replay.rs @@ -342,6 +342,11 @@ impl ReplayTrait { trace_reach(line.at).await; Some(line) } + /// The next host line about `label`, which the job performs once the other side + /// is ready, as the recording's did, then in its step (see `trace_reach`). + fn take_line(label: &Label) -> Option { + proxy::recorder::replay::take_host_line(&LineKey::Handle(label.clone())) + } }; items.extend(ast.items); } @@ -351,59 +356,96 @@ impl ReplayTrait { where T: Replay + ToValue + ValueTyped + wit_stream::StreamPayload, { - // Writes the trace's chunks, then ends the stream, as the trace has it. + // Writes the trace's chunks, then ends the stream, as the trace has it, each + // once the guest reads, as the recording waited for with a zero-length write. fn replay(value: &Value) -> Self { let (mut writer, reader, name) = host_stream::(false); let label = Label { table: TRACE_TABLE, name }; proxy::recorder::replay::pair(&trace_label(value), &label); spawn(async move { loop { - let Some(line) = next_line(&label).await else { - // The guest dropped the stream, which the host saw at its next - // write; the guest doesn't read that, so the host just stops. - drop(writer); - proxy::recorder::replay::check(&guest_line(Event::Drop(label))); + let Some(line) = take_line(&label) else { + // The guest dropped the stream, which the host saw as it waited + // to write; or the trace is cut short, e.g. by a trap. + let (ready, _) = writer.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::replay::check(&guest_line(Event::Drop(label))); + } else { + assert!( + proxy::recorder::replay::trace_cut(), + "the guest read {label:?} after the trace's last write", + ); + std::mem::forget(writer); + } return; }; match line.event { Event::Write(chunk) => { + let (ready, _) = writer.write(Vec::new()).await; + assert!( + !matches!(ready, wit_bindgen::StreamResult::Dropped), + "the guest dropped {label:?} before it read {}", chunk.items, + ); + trace_reach(line.at).await; let items: Vec = Replay::replay(&wave_value::>(&chunk.items)); let rest = writer.write_all(items).await; assert!(rest.is_empty(), "the guest dropped {label:?} before it read {}", chunk.items); } - Event::Drop(_) => return drop(writer), + Event::Drop(_) => { + trace_reach(line.at).await; + return drop(writer); + } event => unreachable!("the host can't do {event:?} with its stream"), } } }); reader } - // Reads as the trace has it, and checks what the guest wrote. + // Reads as the trace has it, each once the guest writes, as the recording + // waited for with a zero-length read, and checks what the guest wrote. fn consume(mut self) { let label = label_of(&self); spawn(async move { loop { - let Some(line) = next_line(&label).await else { - // The guest ended the stream, which the host's next read sees. - let (result, items) = self.read(Vec::with_capacity(1)).await; - assert!( - matches!(result, wit_bindgen::StreamResult::Dropped) && items.is_empty(), - "the guest wrote to {label:?} after the trace's last read", - ); - proxy::recorder::replay::check(&guest_line(Event::Drop(label))); - return; + let Some(line) = take_line(&label) else { + // The guest ends the stream, which the host sees as it waits to + // read; or the trace is cut short, e.g. by a trap, and the + // guest's writes since have no lines: the host takes them. + loop { + let (result, items) = self.read(Vec::with_capacity(4096)).await; + if !items.is_empty() { + assert!( + proxy::recorder::replay::trace_cut(), + "the guest wrote to {label:?} after the trace's last read", + ); + host_dropping(move || drop(items)); + } + if let wit_bindgen::StreamResult::Dropped = result { + proxy::recorder::replay::check(&guest_line(Event::Drop(label))); + return; + } + } }; match line.event { // As many items as the trace has, which the guest's writes // split alike. Event::Read(chunk) => { + let (ready, _) = self.read(Vec::new()).await; + assert!( + !matches!(ready, wit_bindgen::StreamResult::Dropped), + "the guest dropped {label:?} before it wrote {}", chunk.items, + ); + trace_reach(line.at).await; let count = wave_value::>(&chunk.items).unwrap_list().count(); let (_, items) = self.read(Vec::with_capacity(count)).await; let wave = wasm_wave::to_string(&items.to_value()).unwrap(); assert_eq!(wave, chunk.items, "the guest wrote other items to {label:?}"); host_dropping(move || drop(items)); } - Event::Drop(_) => return drop(self), + Event::Drop(_) => { + trace_reach(line.at).await; + return drop(self); + } event => unreachable!("the host can't do {event:?} with the guest's stream"), } } @@ -419,7 +461,8 @@ impl ReplayTrait { where T: Replay + ToValue + ValueTyped + wit_future::FuturePayload, { - // Writes the trace's value, unless the guest dropped the future first. + // Writes the trace's value, which the trace has as the host wrote it, so a + // guest's drop of the future follows if the guest dropped its end instead. fn replay(value: &Value) -> Self { let (writer, reader, name) = host_future::(false); let label = Label { table: TRACE_TABLE, name }; @@ -435,9 +478,11 @@ impl ReplayTrait { let Event::Value(value) = line.event else { unreachable!("the host can't do {:?} with its future", line.event); }; - let wave = value.value.clone(); let value: T = Replay::replay(&wave_value::(&value.value)); - assert!(writer.write(value).await.is_ok(), "the guest dropped {label:?} before it took {wave}"); + if let Err(unwritten) = writer.write(value).await { + proxy::recorder::replay::check(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); + } }); reader } diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 840e742..0f1e9b2 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -488,23 +488,37 @@ where .iter() .map(|item| wasm_wave::to_string(&item.to_value()).unwrap()) .collect(); - let at = trace_at(); - let rest = writer.write_all(chunk).await; - let written = waves.len() - rest.len(); - if written > 0 { - let items = format!("[{}]", waves[..written].join(", ")); - let chunk = Chunk { - stream: label.clone(), - items, - }; - proxy::recorder::record::record( - &host_line_at(at, Event::Write(chunk)), - ); - } - if !rest.is_empty() { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); - host_dropping(move || drop((rest, chunks.collect::>()))); - return; + let (mut items, mut offset) = (chunk, 0); + while !items.is_empty() { + let (ready, _) = writer.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((items, chunks.collect::>()))); + return; + } + let (at, count) = (trace_at(), items.len()); + let (result, rest) = writer.write(items).await; + let rest = rest.into_vec(); + let written = count - rest.len(); + if written > 0 { + let items = format!( + "[{}]", waves[offset..offset + written].join(", ") + ); + let chunk = Chunk { + stream: label.clone(), + items, + }; + proxy::recorder::record::record( + &host_line_at(at, Event::Write(chunk)), + ); + offset += written; + } + if let wit_bindgen::StreamResult::Dropped = result { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((rest, chunks.collect::>()))); + return; + } + items = rest; } } drop(writer); @@ -523,6 +537,11 @@ where spawn(async move { let mut count = 0; while reads != Some(count) { + let (ready, _) = self.read(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + return; + } let at = trace_at(); let (result, items) = self.read(Vec::with_capacity(capacity)).await; if !items.is_empty() { @@ -557,21 +576,14 @@ where let label = Label { table: TRACE_TABLE, name }; let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { - let at = trace_at(); - match writer.write(value).await { - Ok(()) => { - let value = FutureValue { - future: label, - value: wave, - }; - proxy::recorder::record::record( - &host_line_at(at, Event::Value(value)), - ); - } - Err(unwritten) => { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); - host_dropping(move || drop(unwritten)); - } + let line = FutureValue { + future: label.clone(), + value: wave, + }; + proxy::recorder::record::record(&host_line(Event::Value(line))); + if let Err(unwritten) = writer.write(value).await { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); } }); Ok(reader) diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index e2a8647..745b9d3 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -668,23 +668,37 @@ where .iter() .map(|item| wasm_wave::to_string(&item.to_value()).unwrap()) .collect(); - let at = trace_at(); - let rest = writer.write_all(chunk).await; - let written = waves.len() - rest.len(); - if written > 0 { - let items = format!("[{}]", waves[..written].join(", ")); - let chunk = Chunk { - stream: label.clone(), - items, - }; - proxy::recorder::record::record( - &host_line_at(at, Event::Write(chunk)), - ); - } - if !rest.is_empty() { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); - host_dropping(move || drop((rest, chunks.collect::>()))); - return; + let (mut items, mut offset) = (chunk, 0); + while !items.is_empty() { + let (ready, _) = writer.write(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((items, chunks.collect::>()))); + return; + } + let (at, count) = (trace_at(), items.len()); + let (result, rest) = writer.write(items).await; + let rest = rest.into_vec(); + let written = count - rest.len(); + if written > 0 { + let items = format!( + "[{}]", waves[offset..offset + written].join(", ") + ); + let chunk = Chunk { + stream: label.clone(), + items, + }; + proxy::recorder::record::record( + &host_line_at(at, Event::Write(chunk)), + ); + offset += written; + } + if let wit_bindgen::StreamResult::Dropped = result { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop((rest, chunks.collect::>()))); + return; + } + items = rest; } } drop(writer); @@ -703,6 +717,11 @@ where spawn(async move { let mut count = 0; while reads != Some(count) { + let (ready, _) = self.read(Vec::new()).await; + if let wit_bindgen::StreamResult::Dropped = ready { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + return; + } let at = trace_at(); let (result, items) = self.read(Vec::with_capacity(capacity)).await; if !items.is_empty() { @@ -737,21 +756,14 @@ where let label = Label { table: TRACE_TABLE, name }; let wave = wasm_wave::to_string(&value.to_value()).unwrap(); spawn(async move { - let at = trace_at(); - match writer.write(value).await { - Ok(()) => { - let value = FutureValue { - future: label, - value: wave, - }; - proxy::recorder::record::record( - &host_line_at(at, Event::Value(value)), - ); - } - Err(unwritten) => { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); - host_dropping(move || drop(unwritten)); - } + let line = FutureValue { + future: label.clone(), + value: wave, + }; + proxy::recorder::record::record(&host_line(Event::Value(line))); + if let Err(unwritten) = writer.write(value).await { + proxy::recorder::record::record(&guest_line(Event::Drop(label))); + host_dropping(move || drop(unwritten)); } }); Ok(reader) From 8b93278f04d237e0fa0a1ac1c2358f53e81caa8d Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 19:29:45 -0800 Subject: [PATCH 23/25] Add wasi-http-p3 into test and fix things - Replay's mocks, and start's drops of the guest's resources, take the number before a binding's suffix, e.g. 1 of `#request-1-2`. Record mode labels a host resource by the host's handle, which `wasmtime serve` reuses across requests on one instance. - A guest's drop of a host-written future is no longer recorded. Its `value` line is recorded as the host writes it, and ends the label, so a later `drop` became a different binding that replay couldn't match. Replay writes the value and accepts either outcome, taken or dropped. The comment on `value` in recorder.wit says so more plainly. --- Makefile | 24 ++++++- assets/recorder.one_byte_page.wasm | Bin 383789 -> 383790 bytes assets/recorder.wasm | Bin 383790 -> 383791 bytes assets/recorder.wit | 5 +- crates/trace/src/lib.rs | 66 +++++++++++++----- docs/p3-record-replay.md | 39 ++++++++--- src/codegen/record.rs | 19 +++++ src/codegen/replay.rs | 15 ++-- src/traits/fuzz.rs | 5 +- src/traits/proxy.rs | 13 ++-- src/traits/replay.rs | 20 +++--- tests/snapshots/p3-fuzz/record_exports/lib.rs | 1 - tests/snapshots/p3-fuzz/record_imports/lib.rs | 1 - tests/snapshots/replay/record_exports/lib.rs | 26 ++++--- tests/snapshots/replay/record_imports/lib.rs | 2 +- 15 files changed, 164 insertions(+), 72 deletions(-) diff --git a/Makefile b/Makefile index 8ad63c5..df53d9a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-record-p3 test-fuzz-replay test-fuzz test-dialog run-fuzz run-fuzz-replay run-record run-dialog run-viceroy +.PHONY: all build-components build-cli test test-cargo test-record test-fixture test-record-p3 test-record-http-p3 test-fuzz-replay test-fuzz test-dialog run-fuzz run-fuzz-replay run-record run-dialog run-viceroy # `make test ONE_BYTE_PAGE=1` tests with 1-byte-page memories ifeq ($(ONE_BYTE_PAGE),1) @@ -23,7 +23,7 @@ build-components: cp target/one-byte-page/wasm32-wasip2/release/debug.wasm assets/debug.one_byte_page.wasm cp target/one-byte-page/wasm32-wasip2/release/recorder.wasm assets/recorder.one_byte_page.wasm -test: test-cargo test-fuzz test-record test-fixture test-record-p3 test-fuzz-replay test-dialog +test: test-cargo test-fuzz test-record test-fixture test-record-p3 test-record-http-p3 test-fuzz-replay test-dialog # Snapshot tests of the generated sources test-cargo: @@ -102,6 +102,26 @@ test-record-p3: cat $(P3_RECORD_OUT)/replay.out; echo "replay failed, with the output above, of the trace in $(P3_RECORD_OUT)/trace.out"; exit 1; }; \ done +# Records tests/wasi_http_p3.wasm under `wasmtime serve`, which reuses an instance for the requests, +# so its trace has an export call for each, and replays it. Not its echo (see +# docs/p3-record-replay.md, Labels), nor a fetch, which needs the network. +HTTP_OUT = target/fixture-http-p3 +HTTP_ADDR = 127.0.0.1:8789 +test-record-http-p3: + mkdir -p $(HTTP_OUT) + $(INSTRUMENT) -m record tests/wasi_http_p3.wasm + cp composed.wasm $(HTTP_OUT)/record.wasm + $(INSTRUMENT) -m replay tests/wasi_http_p3.wasm + cp composed.wasm $(HTTP_OUT)/replay.wasm + wasmtime serve $(WASMTIME_FLAGS) -S p3,http,cli --no-logging-prefix --addr $(HTTP_ADDR) $(HTTP_OUT)/record.wasm > $(HTTP_OUT)/trace.out 2> $(HTTP_OUT)/serve.err & echo $$! > $(HTTP_OUT)/serve.pid; \ + sleep 2; \ + for path in / "/wait?ms=20" /random "/wait?ms=5" /; do \ + curl -sf -m 10 "http://$(HTTP_ADDR)$$path" > /dev/null || { kill `cat $(HTTP_OUT)/serve.pid`; cat $(HTTP_OUT)/serve.err; echo "GET $$path failed"; exit 1; }; \ + done; \ + kill `cat $(HTTP_OUT)/serve.pid` + $(WASMTIME) --invoke 'start()' $(HTTP_OUT)/replay.wasm < $(HTTP_OUT)/trace.out > $(HTTP_OUT)/replay.out 2>&1 || { \ + cat $(HTTP_OUT)/replay.out; echo "replay failed, with the output above, of the trace in $(HTTP_OUT)/trace.out"; exit 1; } + # The spike's guest, which joins two concurrent results in the order its wakeups receive them RACE = tests/fixtures/spike-p3-race RACE_OUT = target/fixture-race diff --git a/assets/recorder.one_byte_page.wasm b/assets/recorder.one_byte_page.wasm index cb7b2cddd0ea55dca7def2ba85d9af7f4ba0f8b7..60f9b2e96db4f03744edf9707932a8d67fd12446 100644 GIT binary patch delta 5446 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 diff --git a/assets/recorder.wit b/assets/recorder.wit index 02e75ec..46f2170 100644 --- a/assets/recorder.wit +++ b/assets/recorder.wit @@ -32,9 +32,8 @@ interface types { // The host read `items`, which the guest wrote. read(chunk), // A future's value, and who wrote it (`by`). If the host wrote it, the line is recorded when - // the host writes, before the guest can read it; if the guest then drops its end without - // reading, a `drop` by the guest follows. If the guest wrote it, the line is recorded when - // the host reads it. + // the host writes, before the guest can read it; the guest may drop its end instead, which + // isn't recorded. If the guest wrote it, the line is recorded when the host reads it. value(future-value), // `by` is who dropped it. drop(label), diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 7f12fc7..7ecdfd4 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -160,6 +160,10 @@ impl fmt::Display for Label { } } +/// An event as text, after the party that did it (see `Line`'s): a call's, by its task, e.g. +/// `t1.call run(#request-1)` or `c2.return ok`, and a copy's by the direction its data moves +/// for that party, `<--` into the stream or future, `-->` out of it, e.g. the host's write +/// `#stream-6 <-- [52]`, its read `#stream-3 --> [97]`, or a writer's `#future-4 <-- ok`. impl fmt::Display for Event { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -170,17 +174,17 @@ impl fmt::Display for Event { args, }) => { let is_async = if *is_async { "async " } else { "" }; - write!(f, "call {id} {is_async}{func}({})", args.join(", ")) + write!(f, "{id}.call {is_async}{func}({})", args.join(", ")) } - Event::Started(id) => write!(f, "started {id}"), + Event::Started(id) => write!(f, "{id}.started"), Event::Return(ReturnValue { id, value }) => { - write!(f, "return {id} {}", value.as_deref().unwrap_or("()")) + write!(f, "{id}.return {}", value.as_deref().unwrap_or("()")) } - Event::Cancel(id) => write!(f, "cancel {id}"), - Event::Cancelled(id) => write!(f, "cancelled {id}"), - Event::Write(Chunk { stream, items }) => write!(f, "write {stream} {items}"), - Event::Read(Chunk { stream, items }) => write!(f, "read {stream} {items}"), - Event::Value(FutureValue { future, value }) => write!(f, "value {future} {value}"), + Event::Cancel(id) => write!(f, "{id}.cancel"), + Event::Cancelled(id) => write!(f, "{id}.cancelled"), + Event::Write(Chunk { stream, items }) => write!(f, "{stream} <-- {items}"), + Event::Read(Chunk { stream, items }) => write!(f, "{stream} --> {items}"), + Event::Value(FutureValue { future, value }) => write!(f, "{future} <-- {value}"), Event::Drop(handle) => write!(f, "drop {handle}"), } } @@ -446,13 +450,43 @@ mod tests { assert_eq!( lines, [ - "host: call t1 run(#request-1)", - "guest: call c1 get(1)", - "host: return c1 \"a\"", - "guest: return t1 ()", + "host: t1.call run(#request-1)", + "guest: c1.call get(1)", + "host: c1.return \"a\"", + "guest: t1.return ()", "host: drop #session-1 (exports)", ] ); + let label = |name: &str| Label { + table: Table::Imports, + name: name.to_string(), + }; + let chunk = |name: &str, items: &str| Chunk { + stream: label(name), + items: items.to_string(), + }; + let copies = [ + line(Party::Host, Event::Started(task(Table::Imports, 2))), + line(Party::Host, Event::Write(chunk("#stream-6", "[52]"))), + line(Party::Host, Event::Read(chunk("#stream-3", "[97]"))), + line( + Party::Guest, + Event::Value(FutureValue { + future: label("#future-4"), + value: "ok".to_string(), + }), + ), + ]; + let lines: Vec<_> = copies.iter().map(Line::to_string).collect(); + assert_eq!( + lines, + [ + "host: c2.started", + "host: #stream-6 <-- [52]", + "host: #stream-3 --> [97]", + "guest: #future-4 <-- ok", + ] + ); } #[test] @@ -468,7 +502,7 @@ mod tests { #[test] #[should_panic( - expected = "the trace has `guest: call c1 get(1)`, but the guest did `guest: call c1 get(2)`" + expected = "the trace has `guest: c1.call get(1)`, but the guest did `guest: c1.call get(2)`" )] fn replay_mismatch() { let mut replay = Replay::new(p2_trace()); @@ -480,7 +514,7 @@ mod tests { } #[test] - #[should_panic(expected = "the trace has `guest: call c1 get(1)`, which replay didn't reach")] + #[should_panic(expected = "the trace has `guest: c1.call get(1)`, which replay didn't reach")] fn replay_missing_guest_line() { let mut replay = Replay::new(p2_trace()); // The guest's lines may still come while `start` waits for the next line's step. @@ -517,7 +551,7 @@ mod tests { line(Party::Host, call(t2, "new", &[])), line(Party::Guest, ret(t2, Some("#session-3"))), ]); - assert_eq!(trace.0[4].to_string(), "guest: return t2 #session-3-2"); + assert_eq!(trace.0[4].to_string(), "guest: t2.return #session-3-2"); let mut replay = Replay::new(trace); replay.next_host_line(); // Replay's table numbers the session differently. @@ -585,7 +619,7 @@ mod tests { } #[test] - #[should_panic(expected = "the trace has `guest: return t2 #session-3`")] + #[should_panic(expected = "the trace has `guest: t2.return #session-3`")] fn replay_checks_labels() { let (t1, t2) = (task(Table::Exports, 1), task(Table::Exports, 2)); let trace = Trace(vec![ diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 195f7d4..5dc910c 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -54,7 +54,7 @@ in component-ui's `docs/p3.md`. Backward compatibility with p2 traces is not a goal; the format is redesigned. A trace is the list of lines the proxy observed, in the order it observed them. Replay relies on only part of that order; see Order, below. How a trace is stored is up to the implementation: the recorders write -one JSON line per line, and the bundled recorder prints text instead, e.g. `host @2: call t1 …`, +one JSON line per line, and the bundled recorder prints text instead, e.g. `host @2: t1.call …`, with `PROXY_TRACE=text`, for reading a fuzz run. The format is defined once, as the WIT interface `proxy:recorder/types` @@ -104,9 +104,8 @@ variant event { // A copy: the host read the items, which the guest wrote. read(chunk), // A future's value, and who wrote it (`by`). If the host wrote it, the line is recorded when - // the host writes, before the guest can read it; if the guest then drops its end without - // reading, a `drop` by the guest follows. If the guest wrote it, the line is recorded when - // the host reads it. + // the host writes, before the guest can read it; the guest may drop its end instead, which + // isn't recorded. If the guest wrote it, the line is recorded when the host reads it. value(future-value), // A stream or future end, or a resource, was dropped; `by` is who dropped it. drop(label), @@ -264,6 +263,16 @@ Limits: reader of it just doesn't see the two labels as one stream. The [core-level shim](#portability) could carry the label through, as the end keeps one handle in the guest's table from when it arrives until the guest passes it on. +- **An end the proxy gave the guest, coming back, can't be copied from, unless its payload is + numeric.** The end is the reader of a pair the proxy made, e.g. a pump's, or a mock's in fuzz + and replay modes, whose writer the proxy keeps. Reading it in the proxy copies between two ends + of one component instance, which wasmtime traps for a payload that isn't numeric ("cannot read + from and write to intra-component future/stream with non-numeric payload"), as the component + model requires. So recording a guest that hands such a future or stream back traps, e.g. an + HTTP echo that passes a request's trailers, a `future, error-code>>`, + on to its response; a `stream`, e.g. the body, is copied. The proxy can't tell that the end + is its own, as it comes back as a new handle. Pairs made in another component instance would + avoid the trap, at the cost of a function per payload type to make them and pump them there. - **Replay compares labels up to renaming.** A table holds more than the values the trace labels: waitable sets and subtasks, which each mode makes its own, so the same value can have another number in replay. Replay pairs each of the trace's labels with the one it sees for the @@ -547,7 +556,13 @@ or blocks first. ### Hosts -- Recording a wasi-http component under `wasmtime serve -S p3,http`, or viceroy, needs no change. +- Recording a wasi-http component under `wasmtime serve -S p3,http,cli --no-logging-prefix` + needs no change: `cli` for the WASI 0.2 interfaces a component built for `wasm32-wasip2` also + imports, and no prefix so that the output is the trace. wasmtime reuses an instance for several + requests of a p3 component, so a trace has an export call for each of them, which replay + makes in turn; an instance's trace is one recording. Recording `tests/wasi_http_p3.wasm` this + way replays, for each of its routes but its echo (see [Labels](#labels)). Viceroy is for p2 + components. - Recording under `proxy-component run` passes the main component's p3 imports through to the host, so `run` needs `wasmtime_wasi::p3` and wasmtime-wasi-http's p3 linker, and, for `wasi:http/handler`, a way to receive requests, as `--invoke` can't build one. @@ -666,10 +681,10 @@ shim](#portability) could provide; see No guest task on import calls, in - **A fixpoint test**: record a run, replay it while recording, and require the two traces to be equal. The spike does this, for its scenarios. - **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2, p3 and p3-record - fixtures, `wasi_http_p3`, and the spike's guest, and replays each trace, which has to end as the fuzzing - run did: to the end, or in the guest's trap, e.g. a panic on an error the fuzzed host returned, - which is what fuzzing finds. A trap in a generated component or the recorder, e.g. a - divergence, fails it (`tests/run-wasm.sh`). + fixtures, `wasi_http_p3`, and the spike's guest, and replays each trace, which has to end as + the fuzzing run did: to the end, or in the guest's trap, e.g. a panic on an error the fuzzed + host returned, which is what fuzzing finds. A trap in a generated component or the recorder, + e.g. a divergence, fails it (`tests/run-wasm.sh`). - **Enumerated scenarios**: for small guests, every placement of host completions across steps, rather than random delays, and guests that merge nondeterministically, which are the ones steps exist for. @@ -733,8 +748,10 @@ depend on runtime patches. wasmtime 49 has the start of its own record and repla 4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. Async imports, streams and futures are done: `make test-record-p3` records `tests/fixtures/p3-record` under `wasmtime run`, with delays that make its calls RETURNED or STARTED, and stdin copied to - stderr through their streams, and replays each trace. Next are the hosts: `wasmtime serve`, - and `proxy-component run`. + stderr through their streams, and replays each trace; `make test-record-http-p3` records + `tests/wasi_http_p3.wasm` under `wasmtime serve`, for several requests, and replays it. Next + are `proxy-component run` as a host, and the ends the guest hands back (see + [Labels](#labels)). 5. **Portability across runtimes**: the profile proposal, or the shim. ## Open questions diff --git a/src/codegen/record.rs b/src/codegen/record.rs index afdeba2..bd0948f 100644 --- a/src/codegen/record.rs +++ b/src/codegen/record.rs @@ -29,6 +29,9 @@ impl State { let ident = &arg.ident; if import_arg.is_borrowed { parse_quote! { &#ident } + } else if is_option_of_ref(&import_arg.ty) { + // E.g. `Option<&str>` of an `Option`, or `Option<&T>` of an `Option`. + parse_quote! { #ident.as_ref().map(std::borrow::Borrow::borrow) } } else { parse_quote! { #ident } } @@ -136,3 +139,19 @@ impl State { } } } + +/// Whether `ty` is an `Option` of a reference, e.g. `Option<&str>`, as wit-bindgen borrows inside +/// an `option` the parameters of imports. +fn is_option_of_ref(ty: &syn::Type) -> bool { + let syn::Type::Path(path) = ty else { + return false; + }; + let Some(segment) = path.path.segments.last() else { + return false; + }; + let syn::PathArguments::AngleBracketed(args) = &segment.arguments else { + return false; + }; + segment.ident == "Option" + && matches!(args.args.first(), Some(syn::GenericArgument::Type(syn::Type::Reference(_)))) +} diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 77974ab..3a61d9f 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -101,13 +101,16 @@ impl State { }); Some(quote! { Event::Drop(label) => { - let (name, handle) = label - .name - .strip_prefix('#') - .and_then(|label| label.rsplit_once('-')) - .and_then(|(name, handle)| Some((name, handle.parse::().ok()?))) + // E.g. `#session-1`, or `#session-1-2` for a later binding of the number (see + // `crates/trace`'s renaming): the name ends before the first number. + let label = label.name.strip_prefix('#').expect("invalid handle label"); + let words: Vec<&str> = label.split('-').collect(); + let number = words + .iter() + .position(|word| word.parse::().is_ok()) .expect("invalid handle label"); - match name { + let (name, handle) = (words[..number].join("-"), words[number].parse::().unwrap()); + match name.as_str() { #(#arms)* _ => unreachable!(), } diff --git a/src/traits/fuzz.rs b/src/traits/fuzz.rs index eccfdc5..7696d89 100644 --- a/src/traits/fuzz.rs +++ b/src/traits/fuzz.rs @@ -449,8 +449,8 @@ impl FuzzTrait { T: Fuzz + ToValue + ValueTyped + wit_future::FuturePayload, { // Writes a fuzzed value, whose line is recorded as it is written, before the guest - // can take it and go on, e.g. trap; a guest's drop of the future follows it if - // the guest drops its end instead. + // can take it and go on, e.g. trap. The guest may drop its end instead, which + // isn't recorded: the line ends the label, and replay writes the value alike. fn fuzz(u: &mut Unstructured<'_>) -> Result { let value = T::fuzz(u)?; let (writer, reader, name) = host_future::(true); @@ -460,7 +460,6 @@ impl FuzzTrait { let line = FutureValue { future: label.clone(), value: wave }; proxy::recorder::record::record(&host_line(Event::Value(line))); if let Err(unwritten) = writer.write(value).await { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); host_dropping(move || drop(unwritten)); } }); diff --git a/src/traits/proxy.rs b/src/traits/proxy.rs index 7a02511..9bf90a7 100644 --- a/src/traits/proxy.rs +++ b/src/traits/proxy.rs @@ -511,10 +511,10 @@ fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { } } /// Passes the value of a future from `from`, which `sender` writes, to `to`, which - /// the other party reads, unless it drops it first. The value's line is recorded as - /// the pump writes it, before the reader can take it and go on, e.g. trap; a drop - /// follows if the reader drops its end instead. A future can't wait until the other - /// side reads: the host's read of the guest's value is recorded once it completes. + /// the other party reads, unless it drops it first, which isn't recorded. The value's + /// line is recorded as the pump writes it, before the reader can take it and go on, + /// e.g. trap. A future can't wait until the other side reads, so the guest's value is + /// recorded once the pump's read of it completes. async fn pump_future( from: wit_bindgen::FutureReader, to: wit_bindgen::FutureWriter, @@ -534,10 +534,7 @@ fn pump_defs(has_streams: bool, has_futures: bool) -> Vec { }; let value_line = Event::Value(FutureValue { future: label.clone(), value: wave }); proxy::recorder::record::record(&pump_line(sender, trace_at(), value_line)); - if to.write(value).await.is_err() { - let line = pump_line(trace_receiver(sender), trace_at(), Event::Drop(label)); - proxy::recorder::record::record(&line); - } + drop(to.write(value).await); } }; items.extend(file.items); diff --git a/src/traits/replay.rs b/src/traits/replay.rs index 2da81d5..eef0696 100644 --- a/src/traits/replay.rs +++ b/src/traits/replay.rs @@ -41,10 +41,12 @@ impl Trait for ReplayTrait { parse_quote! { impl Replay for #resource_path { fn replay(value: &Value) -> Self { + // The number, also of a later binding of it, e.g. `request-1-2` (see + // `crates/trace`'s renaming). let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", #wit_name)) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); #mock } @@ -57,7 +59,7 @@ impl Trait for ReplayTrait { let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", #wit_name)) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); SCOPED_ALLOC.with(|alloc| { let mut alloc = alloc.borrow_mut(); @@ -74,10 +76,12 @@ impl Trait for ReplayTrait { parse_quote! { impl Replay for #resource_path { fn replay(value: &Value) -> Self { + // The number, also of a later binding of it, e.g. `request-1-2` (see + // `crates/trace`'s renaming). let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", #wit_name)) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); #resource_path::new(MockedResource { handle, @@ -461,18 +465,17 @@ impl ReplayTrait { where T: Replay + ToValue + ValueTyped + wit_future::FuturePayload, { - // Writes the trace's value, which the trace has as the host wrote it, so a - // guest's drop of the future follows if the guest dropped its end instead. + // Writes the trace's value, which the trace has as the host wrote it, whether + // the guest took it, or dropped its end instead. fn replay(value: &Value) -> Self { let (writer, reader, name) = host_future::(false); let label = Label { table: TRACE_TABLE, name }; proxy::recorder::replay::pair(&trace_label(value), &label); spawn(async move { let Some(line) = next_line(&label).await else { - // The trace has no value to write; the guest dropped its end, so - // nothing sees the writer that is kept unwritten. + // The host never wrote it, e.g. the run ended first, so nothing + // sees the writer that is kept unwritten. std::mem::forget(writer); - proxy::recorder::replay::check(&guest_line(Event::Drop(label))); return; }; let Event::Value(value) = line.event else { @@ -480,7 +483,6 @@ impl ReplayTrait { }; let value: T = Replay::replay(&wave_value::(&value.value)); if let Err(unwritten) = writer.write(value).await { - proxy::recorder::replay::check(&guest_line(Event::Drop(label))); host_dropping(move || drop(unwritten)); } }); diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 0f1e9b2..57b0c9e 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -582,7 +582,6 @@ where }; proxy::recorder::record::record(&host_line(Event::Value(line))); if let Err(unwritten) = writer.write(value).await { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); host_dropping(move || drop(unwritten)); } }); diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index 745b9d3..a110a2a 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -762,7 +762,6 @@ where }; proxy::recorder::record::record(&host_line(Event::Value(line))); if let Err(unwritten) = writer.write(value).await { - proxy::recorder::record::record(&guest_line(Event::Drop(label))); host_dropping(move || drop(unwritten)); } }); diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index 434d954..6f8db6c 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -124,16 +124,20 @@ impl exports::proxy::recorder::start_replay::Guest for Stub { } } Event::Drop(label) => { - let (name, handle) = label + let label = label .name .strip_prefix('#') - .and_then(|label| label.rsplit_once('-')) - .and_then(|(name, handle)| Some(( - name, - handle.parse::().ok()?, - ))) .expect("invalid handle label"); - match name { + let words: Vec<&str> = label.split('-').collect(); + let number = words + .iter() + .position(|word| word.parse::().is_ok()) + .expect("invalid handle label"); + let (name, handle) = ( + words[..number].join("-"), + words[number].parse::().unwrap(), + ); + match name.as_str() { "session" => { drop(unsafe { test::guest::api::Session::from_handle(handle) @@ -355,7 +359,7 @@ impl Replay for test::guest::api::Session { let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "session")) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); { fn cannot_mock() -> T { @@ -374,7 +378,7 @@ impl<'a> Replay for &'a test::guest::api::Session { let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "session")) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); SCOPED_ALLOC .with(|alloc| { @@ -472,7 +476,7 @@ impl Replay for test::host::types::Counter { let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "counter")) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); proxy::conversion::conversion::get_mock_test_host_types_counter(handle) } @@ -482,7 +486,7 @@ impl<'a> Replay for &'a test::host::types::Counter { let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("borrow-{}-", "counter")) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); SCOPED_ALLOC .with(|alloc| { diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index c903b68..bb6c4a1 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -546,7 +546,7 @@ impl Replay for exports::test::host::types::Counter { let label = value.unwrap_handle(); let handle = label .strip_prefix(&format!("{}-", "counter")) - .and_then(|s| s.parse::().ok()) + .and_then(|s| s.split('-').next()?.parse::().ok()) .expect("invalid handle label"); exports::test::host::types::Counter::new(MockedResource { handle, From 85d05400d8582279cb1a443fc12642e1125be0fd Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 20:00:39 -0800 Subject: [PATCH 24/25] Record cancel events; reveal the multiple events in the same waitable set limit. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - How it's recorded: a guard in each async import's wrapper records the guest's Cancel and the host's Cancelled, in record and fuzz modes. In record mode, the host's call is cancelled with the wrapper. - Replay: a STARTED call whose trace answers Cancelled has its mock wait until the guest cancels, then the guard checks the Cancel. - Test: the p3-record fixture has a new race export, which awaits wait-for(a) and wait-for(b) and drops the slower one. Six recordings with different delays all replay, with the Cancel when the slower call was still pending. These cases are now in make test-record-p3, which passes. - The other fuzz-replay suites still replay fully: p3 60 of 60, wasi_http_p3 30 of 30, race guest 30 of 30, p2 20 of 20. - The design doc describes how cancellation is recorded. The host cancelling an export call is still not covered. The problem this leaves: p3-record's fuzz-replay is now at 77 of 80. 37 traces contain a cancel, so race is exercised often. make test-fuzz-replay runs p3-record for 10 rounds, so a full make test would fail now and then. --- Makefile | 10 ++-- docs/p3-record-replay.md | 8 ++++ src/codegen/fuzz.rs | 5 +- src/codegen/mod.rs | 48 ++++++++++++++++++- src/codegen/record.rs | 7 ++- src/codegen/replay.rs | 7 ++- tests/fixtures/p3-record/guest/src/lib.rs | 18 ++++++- tests/snapshots/fuzz/record_exports/lib.rs | 30 ++++++++++++ tests/snapshots/fuzz/record_imports/lib.rs | 30 ++++++++++++ tests/snapshots/p3-fuzz/record_exports/lib.rs | 30 ++++++++++++ tests/snapshots/p3-fuzz/record_imports/lib.rs | 45 +++++++++++++++-- tests/snapshots/record/record_exports/lib.rs | 30 ++++++++++++ tests/snapshots/record/record_imports/lib.rs | 30 ++++++++++++ tests/snapshots/replay/record_exports/lib.rs | 23 +++++++++ tests/snapshots/replay/record_imports/lib.rs | 29 ++++++++++- 15 files changed, 335 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index df53d9a..3ec1269 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,8 @@ test-fixture: # Records the main component of tests/fixtures/p3-record/guest, whose async imports, streams # and futures wasmtime implements itself, and replays each trace: `run` with each pair of delays, -# RETURNED for 0, or random ones, and `cat` with stdin in one chunk, in several, or none. +# RETURNED for 0, or random ones, `race`, which cancels the call that loses, and `cat` with stdin +# in one chunk, in several, or none. P3_RECORD = tests/fixtures/p3-record/guest P3_RECORD_OUT = target/fixture-p3-record P3_RECORD_WASM = $(P3_RECORD_OUT)/wasm32-wasip2/release/proxy_p3_record_guest.wasm @@ -88,9 +89,10 @@ test-record-p3: cp composed.wasm $(P3_RECORD_OUT)/record.wasm $(INSTRUMENT) -m replay $(P3_RECORD_WASM) cp composed.wasm $(P3_RECORD_OUT)/replay.wasm - for delays in "5 15" "15 5" "0 10" "10 0" "0 0" "5 5" random random random random random; do \ - set -- $$delays; env=""; [ "$$1" = random ] || env="--env DELAY_A=$$1 --env DELAY_B=$$2"; \ - $(WASMTIME) $$env --invoke 'run()' $(P3_RECORD_OUT)/record.wasm > $(P3_RECORD_OUT)/trace.out 2>&1 || { \ + for call in "run 5 15" "run 15 5" "run 0 10" "run 10 0" "run 0 0" "run 5 5" "run random" "run random" "run random" "run random" "run random" \ + "race 5 30" "race 30 5" "race 10 0" "race 0 0" "race random" "race random" "race random"; do \ + set -- $$call; env=""; [ "$$2" = random ] || env="--env DELAY_A=$$2 --env DELAY_B=$$3"; \ + $(WASMTIME) $$env --invoke "$$1()" $(P3_RECORD_OUT)/record.wasm > $(P3_RECORD_OUT)/trace.out 2>&1 || { \ cat $(P3_RECORD_OUT)/trace.out; echo "recording failed, with the output above"; exit 1; }; \ $(WASMTIME) --invoke 'start()' $(P3_RECORD_OUT)/replay.wasm < $(P3_RECORD_OUT)/trace.out > $(P3_RECORD_OUT)/replay.out 2>&1 || { \ cat $(P3_RECORD_OUT)/replay.out; echo "replay failed, with the output above, of the trace in $(P3_RECORD_OUT)/trace.out"; exit 1; }; \ diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 5dc910c..641fb0b 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -138,6 +138,14 @@ What the types don't enforce: `Cancelled`, or return anyway, which is a `Return`, also when its result was already on its way. A call that hasn't started yet is cancelled without the callee seeing it. It goes both ways: the guest can cancel a pending import, and the host a pending export call. + - **The guest's cancel of an import** reaches the proxy as its export's cancellation, which + drops the call's wrapper, and wit-bindgen acknowledges it at once. So the host answers + `Cancelled`, without an `at`, as it answers a call; in record mode, the host's call is + cancelled with it, and a result it returned meanwhile is dropped, which the guest wouldn't + have read either, as it dropped the call. Only a STARTED call can be cancelled. Replay's mock + of a call the trace answers with `Cancelled` waits until the guest cancels it. + - **The host's cancel of an export** isn't recorded yet: the proxy would have to tell whether + the guest then returned or acknowledged it, which wit-bindgen doesn't say. - **Cancel and Drop** are about different things: `Cancel` about a pending call, `Drop` about a handle. - **`Cancel`.** In wit-bindgen, an async import call is a Rust `Future`; dropping it while the diff --git a/src/codegen/fuzz.rs b/src/codegen/fuzz.rs index 01d89df..2cfa526 100644 --- a/src/codegen/fuzz.rs +++ b/src/codegen/fuzz.rs @@ -33,7 +33,10 @@ impl State { let __runs = __host.int_in_range(0..=3u8).unwrap_or(0); let __at = if __runs > 0 { proxy::recorder::record::record(&Line { by: Party::Host, at: None, event: Event::Started(__id) }); - Some(wait_for_runs(__runs as u32).await) + let __cancel = TraceCancel(Some(__id)); + let at = wait_for_runs(__runs as u32).await; + __cancel.done(); + Some(at) } else { None }; diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index fc57e07..8739991 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -98,6 +98,7 @@ pub fn generate( state.output.extend(traits); if matches!(mode, Mode::Record | Mode::Replay | Mode::Fuzz) { state.output.extend(trace_defs()); + state.output.extend(cancel_defs(mode)); } let has_executor = state.has_executor(); if matches!(mode, Mode::Replay | Mode::Fuzz) { @@ -612,8 +613,12 @@ fn job_defs(mode: &Mode, has_executor: Option) -> Vec { } let line = proxy::recorder::replay::take_host_line(&LineKey::Task(id)) .expect("the trace doesn't return the call"); - let Event::Return(ret) = line.event else { - panic!("the trace answers the call with {:?}", line.event); + let ret = match line.event { + Event::Return(ret) => ret, + // The guest cancels the call, which the mock waits for, as the recording's + // didn't return (see `TraceCancel`). + Event::Cancelled(_) => return std::future::pending().await, + event => panic!("the trace answers the call with {event:?}"), }; // A STARTED call blocks, so it returns in a later step than the one it was // called in; if replay is there already, it is ahead of the recording. @@ -748,6 +753,45 @@ fn panic_hook(mode: &Mode) -> TokenStream { } } +/// `TraceCancel`, which an async import's wrapper holds while it waits for its answer, in +/// record, replay and fuzz modes. The guest cancels the call by dropping it while it waits, which +/// drops the wrapper's future, and wit-bindgen answers with `task.cancel`, at once. So the +/// guard's drop, before it returns, is the guest's `cancel`, and the host's `cancelled` that +/// answers it, which record and fuzz modes record, and replay checks. In record mode, the host's +/// call is dropped with the wrapper, which cancels it too. +fn cancel_defs(mode: &Mode) -> Vec { + let on_cancel = match mode { + Mode::Record | Mode::Fuzz => quote! { + proxy::recorder::record::record(&Line { by: Party::Guest, at: None, event: Event::Cancel(id) }); + proxy::recorder::record::record(&Line { by: Party::Host, at: None, event: Event::Cancelled(id) }); + }, + Mode::Replay => quote! { + proxy::recorder::replay::check(&Line { by: Party::Guest, at: None, event: Event::Cancel(id) }); + }, + Mode::Dialog => return Vec::new(), + }; + let file: File = parse_quote! { + /// The guard over an async import call while its wrapper waits (see `cancel_defs` in the + /// generator); `done` once it returns. + #[allow(dead_code)] + struct TraceCancel(Option); + impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } + } + impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + #on_cancel + } + } + } + }; + file.items +} + /// Helpers of record, replay and fuzz modes, which make the lines of `proxy:recorder/types`. Each /// component numbers the calls it sees with its own counter: the exports component the host's /// calls of exports, and the imports component the guest's calls of imports (see diff --git a/src/codegen/record.rs b/src/codegen/record.rs index bd0948f..2fc250c 100644 --- a/src/codegen/record.rs +++ b/src/codegen/record.rs @@ -72,7 +72,12 @@ impl State { // it completed (see `host_result`). (true, true) => ( quote! { None }, - quote! { host_result(__id, async move { #call.await }).await }, + quote! {{ + let __cancel = TraceCancel(Some(__id)); + let res = host_result(__id, async move { #call.await }).await; + __cancel.done(); + res + }}, ), // An export call's arrival is a step, as in fuzz and replay modes. (false, false) => ( diff --git a/src/codegen/replay.rs b/src/codegen/replay.rs index 3a61d9f..ccf133c 100644 --- a/src/codegen/replay.rs +++ b/src/codegen/replay.rs @@ -24,7 +24,12 @@ impl State { // The value of the `return` that answers the call: for an async call, at once or in its // step, as the trace has it RETURNED or STARTED. let answer = if is_async { - quote! { trace_answer(__id, __answers).await } + quote! {{ + let __cancel = TraceCancel(Some(__id)); + let answer = trace_answer(__id, __answers).await; + __cancel.done(); + answer + }} } else { quote! { trace_return_value(__answers) } }; diff --git a/tests/fixtures/p3-record/guest/src/lib.rs b/tests/fixtures/p3-record/guest/src/lib.rs index 6676559..48c5a0b 100644 --- a/tests/fixtures/p3-record/guest/src/lib.rs +++ b/tests/fixtures/p3-record/guest/src/lib.rs @@ -1,13 +1,15 @@ //! A WASI p3 main component for record mode, whose async imports, streams and futures wasmtime //! implements itself: `run` waits for `a` and `b` concurrently, with `wait-for`, and joins them in //! the order its wakeups receive them, as the spike's guest does (see -//! tests/fixtures/spike-p3-race); `cat` copies stdin to stderr, through their streams. +//! tests/fixtures/spike-p3-race); `cat` copies stdin to stderr, through their streams; `race` +//! waits for `a` and `b`, and cancels the one that hasn't returned by the time the other has. wit_bindgen::generate!({ inline: r#" package test:rec@0.1.0; interface api { run: async func() -> string; cat: async func() -> string; + race: async func() -> string; } world root { export api; @@ -62,6 +64,20 @@ impl exports::test::rec::api::Guest for Guest { .await; format!("{out} (wakeups: {})", wakeups.join(" ")) } + async fn race() -> String { + let mut a = Box::pin(wait("a")); + let mut b = Box::pin(wait("b")); + let first = std::future::poll_fn(|cx| { + if let Poll::Ready(value) = a.as_mut().poll(cx) { + return Poll::Ready(value); + } + b.as_mut().poll(cx) + }) + .await; + // Drops the other, which cancels it if it's still pending. + drop((a, b)); + format!("{first} first") + } async fn cat() -> String { let (mut stdin, stdin_done) = wasip3::cli::stdin::read_via_stream(); let (mut writer, reader) = wasip3::wit_stream::new::(); diff --git a/tests/snapshots/fuzz/record_exports/lib.rs b/tests/snapshots/fuzz/record_exports/lib.rs index 8c25ae2..f0a8c95 100644 --- a/tests/snapshots/fuzz/record_exports/lib.rs +++ b/tests/snapshots/fuzz/record_exports/lib.rs @@ -683,6 +683,36 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Cancelled(id), + }, + ); + } + } +} #[allow(dead_code)] const TRACE_TABLE: Table = Table::Exports; #[allow(dead_code)] diff --git a/tests/snapshots/fuzz/record_imports/lib.rs b/tests/snapshots/fuzz/record_imports/lib.rs index d16eb6a..12acf3d 100644 --- a/tests/snapshots/fuzz/record_imports/lib.rs +++ b/tests/snapshots/fuzz/record_imports/lib.rs @@ -794,6 +794,36 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Cancelled(id), + }, + ); + } + } +} #[allow(dead_code)] const TRACE_TABLE: Table = Table::Imports; #[allow(dead_code)] diff --git a/tests/snapshots/p3-fuzz/record_exports/lib.rs b/tests/snapshots/p3-fuzz/record_exports/lib.rs index 57b0c9e..da16986 100644 --- a/tests/snapshots/p3-fuzz/record_exports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_exports/lib.rs @@ -680,6 +680,36 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Cancelled(id), + }, + ); + } + } +} #[allow(dead_code)] const TRACE_TABLE: Table = Table::Exports; #[allow(dead_code)] diff --git a/tests/snapshots/p3-fuzz/record_imports/lib.rs b/tests/snapshots/p3-fuzz/record_imports/lib.rs index a110a2a..b3d5ab3 100644 --- a/tests/snapshots/p3-fuzz/record_imports/lib.rs +++ b/tests/snapshots/p3-fuzz/record_imports/lib.rs @@ -80,7 +80,10 @@ impl exports::test::p3::host::Guest for Stub { event: Event::Started(__id), }, ); - Some(wait_for_runs(__runs as u32).await) + let __cancel = TraceCancel(Some(__id)); + let at = wait_for_runs(__runs as u32).await; + __cancel.done(); + Some(at) } else { None }; @@ -123,7 +126,10 @@ impl exports::test::p3::host::Guest for Stub { event: Event::Started(__id), }, ); - Some(wait_for_runs(__runs as u32).await) + let __cancel = TraceCancel(Some(__id)); + let at = wait_for_runs(__runs as u32).await; + __cancel.done(); + Some(at) } else { None }; @@ -261,7 +267,10 @@ impl exports::test::p3::host::GuestBody for MockedResource { event: Event::Started(__id), }, ); - Some(wait_for_runs(__runs as u32).await) + let __cancel = TraceCancel(Some(__id)); + let at = wait_for_runs(__runs as u32).await; + __cancel.done(); + Some(at) } else { None }; @@ -860,6 +869,36 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Cancelled(id), + }, + ); + } + } +} #[allow(dead_code)] const TRACE_TABLE: Table = Table::Imports; #[allow(dead_code)] diff --git a/tests/snapshots/record/record_exports/lib.rs b/tests/snapshots/record/record_exports/lib.rs index a3ba16f..a79f8b6 100644 --- a/tests/snapshots/record/record_exports/lib.rs +++ b/tests/snapshots/record/record_exports/lib.rs @@ -1380,6 +1380,36 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Cancelled(id), + }, + ); + } + } +} /// A line of the guest, which record and fuzz modes record, and replay checks. #[allow(dead_code)] fn trace_guest(line: &Line) { diff --git a/tests/snapshots/record/record_imports/lib.rs b/tests/snapshots/record/record_imports/lib.rs index 3d9a1ae..ca676e1 100644 --- a/tests/snapshots/record/record_imports/lib.rs +++ b/tests/snapshots/record/record_imports/lib.rs @@ -1481,6 +1481,36 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::record::record( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + proxy::recorder::record::record( + &Line { + by: Party::Host, + at: None, + event: Event::Cancelled(id), + }, + ); + } + } +} thread_local! { static SENDER : std::cell::Cell < Party > = const { std::cell::Cell::new(Party::Host) }; diff --git a/tests/snapshots/replay/record_exports/lib.rs b/tests/snapshots/replay/record_exports/lib.rs index 6f8db6c..f14e4b7 100644 --- a/tests/snapshots/replay/record_exports/lib.rs +++ b/tests/snapshots/replay/record_exports/lib.rs @@ -651,6 +651,29 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::replay::check( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + } + } +} #[allow(dead_code)] const TRACE_TABLE: Table = Table::Exports; #[allow(dead_code)] diff --git a/tests/snapshots/replay/record_imports/lib.rs b/tests/snapshots/replay/record_imports/lib.rs index bb6c4a1..3cbb0e6 100644 --- a/tests/snapshots/replay/record_imports/lib.rs +++ b/tests/snapshots/replay/record_imports/lib.rs @@ -710,6 +710,29 @@ fn trace_return_value(answers: Vec) -> Option { }) .expect("the trace doesn't answer the call") } +/// The guard over an async import call while its wrapper waits (see `cancel_defs` in the +/// generator); `done` once it returns. +#[allow(dead_code)] +struct TraceCancel(Option); +impl TraceCancel { + #[allow(dead_code)] + fn done(mut self) { + self.0 = None; + } +} +impl Drop for TraceCancel { + fn drop(&mut self) { + if let Some(id) = self.0 { + proxy::recorder::replay::check( + &Line { + by: Party::Guest, + at: None, + event: Event::Cancel(id), + }, + ); + } + } +} #[allow(dead_code)] const TRACE_TABLE: Table = Table::Imports; #[allow(dead_code)] @@ -738,8 +761,10 @@ async fn trace_answer(id: TaskId, answers: Vec) -> Option { } let line = proxy::recorder::replay::take_host_line(&LineKey::Task(id)) .expect("the trace doesn't return the call"); - let Event::Return(ret) = line.event else { - panic!("the trace answers the call with {:?}", line.event); + let ret = match line.event { + Event::Return(ret) => ret, + Event::Cancelled(_) => return std::future::pending().await, + event => panic!("the trace answers the call with {event:?}"), }; let at = line.at.expect("a STARTED call returns in a step"); let now = current_step(); From fff961c030b104ef17f55c8a41a425121ec6d4db Mon Sep 17 00:00:00 2001 From: Yan Chen Date: Fri, 6 Nov 2026 22:02:51 -0800 Subject: [PATCH 25/25] docs --- docs/p3-record-replay.md | 1167 ++++++++++++++------------------------ 1 file changed, 433 insertions(+), 734 deletions(-) diff --git a/docs/p3-record-replay.md b/docs/p3-record-replay.md index 641fb0b..80138f4 100644 --- a/docs/p3-record-replay.md +++ b/docs/p3-record-replay.md @@ -1,786 +1,485 @@ # Record and replay of WASI p3 components -proxy-component records and replays p2 components, fuzzes and mocks (dialog mode) p3 components, -and replays the traces fuzz mode writes for them. This file is the design for recording and -replaying p3 components: the trace format, -how the proxy records a run without changing it, how replay reproduces it, and what makes replay -portable across runtimes. What is implemented so far is listed below, and in [Phases](#phases). - -It builds on the cross-tool design in component-ui's -[`docs/p3.md`](https://github.com/chenyan2002/component-ui/blob/main/docs/p3.md), and was written -against wasmtime 49, wit-bindgen 0.63 (the `fix-async` fork) and proxy-component after fuzz and -dialog support for p3. - -## What exists - -- **Fuzz and dialog modes support p3.** The generated imports component mocks every import of the - main component, from its WIT, including async functions, streams, futures and error-contexts. - The generated `start` function is async and awaits async exports. -- **`proxy:conversion/executor`** ([`assets/executor.rs`](../assets/executor.rs)) runs the - virtualized host's end of streams and futures in the imports component. A sync mock, e.g. of - `consume-body`, can't block or spawn a task, so it queues the work, and the executor runs it in - the task of its `run` export, which `start` calls. The exports component's work is made by - `start` itself, so it spawns on the task of `start`. Fuzz mode waits for both at the end of - `start`; dialog mode drains them after each export call. -- **The trace format** ([Trace format](#trace-format)) and its recorder interface. Fuzz mode - writes it for p2 and p3 components, with [steps](#steps) and [labels](#labels), and record mode - for p2 components. -- **Replay** of p2 and p3 traces ([Replay](#replay)): calls and steps, RETURNED and STARTED, - streams, futures and error-contexts. -- **Record mode for p3**: async imports, RETURNED or STARTED, with their steps, and streams and - futures, through pumps. +proxy-component records a run of a component and replays it without the host. +This is the design for components that use WASI p3, the component +model's async: async functions, streams, futures and error-contexts. It covers what a trace +records, how the proxy records a run without changing it, how replay reproduces it, and what +makes replay portable across runtimes. [Status](#status) says what is implemented. -## Where nondeterminism comes from +## Overview + +The proxy is two generated components around the main component, the guest. The imports +component stands between the guest and the host: in record mode, it forwards each import to the +host and records it, and in fuzz, dialog and replay modes it plays the host. The exports +component stands between the host and the guest's exports: in record mode the host calls them +through it, and in the other modes, its `start` function makes the calls, as the trace. -Component model concurrency is cooperative: within one component instance one thread runs at a -time, and it observes other threads, or the host, only at defined points. Two kinds of choices -decide a run: +Every mode of the proxy has the same structure, so that a run is the same in record and replay. +In particular, the imports component runs all of the host's side of the run in one task, the +executor, in whose runs a trace's positions are counted (see [Steps](#steps)). -- **The host's timing**: when an async import returns, whether it returns at once (RETURNED) or - starts a subtask (STARTED), when and how much the host reads from or writes to a stream or - future, and when the host calls an export. -- **The runtime's choices** at the points `Concurrency.md` (§Nondeterminism) lists: which pending - event `waitable-set.wait` or `poll` returns, which ready thread resumes, whether a wait with an - event already pending blocks, the order of tasks released from backpressure, and which thread - receives a cancellation. +## Where nondeterminism comes from -Calls between components, and streams and futures with wasm at both ends, add no choices of -their own. wasmtime 49 makes the runtime's choices without randomness: the lowest waitable first, -fixed run queues. jco makes them differently (join order, the oldest ready thread); see the table -in component-ui's `docs/p3.md`. +Component model concurrency is cooperative: within an instance one thread runs at a time, and it +observes other threads, or the host, only at defined points. Two kinds of choices decide a run: + +- **The host's timing**: when an async import returns, and whether at once (RETURNED) or after + starting a subtask (STARTED); when and how much the host reads from or writes to a stream or + future; and when the host calls an export. +- **The runtime's choices**, which the component model leaves open: which of several pending + events a wait returns, which ready thread resumes, and the like. + +A trace records the first. The second must be the same in record and replay: wasmtime makes its +choices without randomness, from the state of the whole store, which replay has to reproduce +closely enough. + +## wasmtime and wit-bindgen + +The design depends on how wasmtime (49) schedules async work, and on how wit-bindgen (0.63) +drives a task. + +### wasmtime + +- **Run queues.** wasmtime keeps a high-priority queue, e.g. delivering an event to a task that + waits for it, or starting a call, and a low-priority one, for a task that yielded; it takes from + the first while there is work in it. So after the executor yields to wake itself, every task with + a pending event, e.g. the guest, runs before the executor's next run. +- **Which pending event a wait returns.** A waitable set keeps its pending events in a + `BTreeSet`, ordered by kind (host task, guest task, stream or future end) and then by + index in a table the whole store shares, which reuses freed slots. A task gets the lowest one + first, one per callback. The index isn't the guest's handle: it depends on every task and resource + in the whole shared store, including the proxy's and the recorder's. This makes the replay difficult + to replicate the exact same scheduling, because the replay composition uses different import and export + components. See [Answers in the same step](#answers-in-the-same-step). +- **Events are only delivered to a waiting task.** An event for a task that yielded, rather than + waits, stays in its set until the task waits, or polls the set itself. +- **Copies within one instance.** A copy between two ends of one instance traps unless the payload + is numeric ("cannot read from and write to intra-component future/stream with non-numeric + payload"), as the component model requires. It's checked when the copy happens, so a component + can't tell beforehand; see [Ends that come back](#labels). +- **Zero-length copies** ask whether the other side is ready: a zero-length write completes once + the reader reads, and leaves the read pending for the next write; a zero-length read likewise + waits for a write. A host's end may answer at once, without being ready, which wasmtime's do. + Futures have no such copies. +- **A call into a component** runs as a queued work item, which suspends the caller while it stays + entered, and holds back calls into it (`do_not_enter`); a sync WASI 0.2 call, e.g. a print, can + suspend an instance the same way. Either changes the schedule where the proxy can't see it. +- **`wasmtime serve`** reuses an instance for many requests of a p3 component, so one instance's + trace has a call for each. + +### wit-bindgen + +- **A task's callback loop.** wit-bindgen runs a task's futures in one callback: it delivers the + callback's event, if any, polls the futures that were woken, and then, if one of them woke the + task again, polls the task's waitable set itself, delivers an event it finds, and polls again; + otherwise it yields (`YIELD`), or waits on the set (`WAIT`). It polls only woken futures, so a + future that wants another poll must wake itself. +- **After a yield**, wasmtime resumes the task without an event, and wit-bindgen polls its futures + before it takes the events that came meanwhile, which it gets only in a later poll. A task woken + by an event takes it before polling. For the executor, whose polls are steps, that difference + would move an event to the next step in replay, where the executor wakes itself, but not in the + recording, where the event woke it. So the executor's callback wraps wit-bindgen's: resumed after + a yield, its first poll is a probe that only wakes the executor, so that wit-bindgen takes a + pending event, if any, before the run. wit-bindgen's generated glue is patched to call the + wrapper. +- **Waking another task**, e.g. a sync mock queuing a job for the executor, goes through + wit-bindgen's inter-task wakeup, a stream that the sleeping task reads. +- **Spawned futures** run in the task of the export that spawned them, which lives until they end; + the exports component's own jobs run on the task of `start`, not in the executor. +- **A pending call moves between tasks.** The proxy's export polls the host's call once, for + RETURNED, and hands it to the executor otherwise; the executor's poll moves its waitable to the + executor's task. +- **Cancellation.** Dropping a pending import call cancels it; the callee's export task gets the + request, its future is dropped, and wit-bindgen acknowledges with `task.cancel`. So the proxy + always answers a cancel with `cancelled`, and a callee's choice between returning and + acknowledging isn't visible to its caller. +- **Streams and futures.** A write of no items is a zero-length write, and a read with no capacity + a zero-length read. A future's writer dropped unwritten writes a default value, which the proxy + never relies on. ## Trace format -Backward compatibility with p2 traces is not a goal; the format is redesigned. A trace is the list -of lines the proxy observed, in the order it observed them. Replay relies on only part of that -order; see Order, below. How a trace is stored is up to the implementation: the recorders write -one JSON line per line, and the bundled recorder prints text instead, e.g. `host @2: t1.call …`, -with `PROXY_TRACE=text`, for reading a fuzz run. - -The format is defined once, as the WIT interface `proxy:recorder/types` -([`assets/recorder.wit`](../assets/recorder.wit)), which the generated components use to record -and check lines. `crates/trace` generates its Rust types from it, and the recorders use those: -the bundled recorder maps the interface to them, and the host recorder of `run` converts its own -bindings' types to them. Values are WAVE text, e.g. `ok(#response-3)`, whose type comes from -where they appear: a function's parameter or result, or a stream's or future's payload. +A trace is the list of lines the proxy observed, in the order it observed them. Its types are +defined once, in WIT ([`assets/recorder.wit`](../assets/recorder.wit)); values are WAVE text, +e.g. `ok(#response-3)`, whose type comes from where they appear. ```wit enum party { host, guest } -// A generated component, which numbers handles and tasks: the imports component, whose handle -// table holds the ends that cross at the main component's imports, and which numbers the guest's -// calls of imports, and the exports component, for its exports. +// The generated component that numbers a handle or a call: the imports component, for the +// guest's imports, and the exports component, for its exports. enum table { imports, exports } -// A call, numbered in order by the generated component that sees it, its `table`: the exports -// component numbers the host's calls of exports, written `t1, t2, …`, and the imports component -// the guest's calls of imports, written `c1, c2, …`. Sync calls are numbered too. +// A call, numbered in order by the component that sees it: `t1, t2, …` for the host's calls of +// exports, `c1, c2, …` for the guest's calls of imports. record task-id { table: table, id: u32 } -// A handle: the table its number is in, and its name as WAVE writes it, its type and its number, -// e.g. `#stream-7` or `#fields-3`; see Labels. +// A handle, by its table and its name, e.g. `#stream-7`; see Labels. record label { table: table, name: string } record call { id: task-id, is-async: bool, %func: string, args: list } -// The callee's result, `none` for a function without one. record return-value { id: task-id, value: option } -// The items one copy of a stream moved. record chunk { %stream: label, items: string } record future-value { %future: label, value: string } variant event { - // `by` is the caller: the host for an export, the guest for an import. - call(call), - // The callee blocked before returning, so the caller got a subtask. Only for an async call. - started(task-id), - %return(return-value), - // The caller asked to cancel a pending call. The callee answers with `cancelled`, or with a - // `return`, if it returns anyway. - cancel(task-id), - // The callee acknowledged a cancellation instead of returning. - cancelled(task-id), - // A copy: the host wrote the items, which the guest read. - write(chunk), - // A copy: the host read the items, which the guest wrote. - read(chunk), - // A future's value, and who wrote it (`by`). If the host wrote it, the line is recorded when - // the host writes, before the guest can read it; the guest may drop its end instead, which - // isn't recorded. If the guest wrote it, the line is recorded when the host reads it. - value(future-value), - // A stream or future end, or a resource, was dropped; `by` is who dropped it. - drop(label), + call(call), // by the caller + started(task-id), // the callee blocked, so the caller got a subtask + %return(return-value), // by the callee + cancel(task-id), // the caller asked to cancel a pending call + cancelled(task-id), // the callee acknowledged it, instead of returning + write(chunk), // the host wrote the items, which the guest read + read(chunk), // the host read the items, which the guest wrote + value(future-value), // a future's value, by its writer + drop(label), // a handle was dropped, by whoever dropped it } -// One event, who did it, and, for a host line the guest observes outside its calls, the step in -// which it happened: a run of the imports component's executor, or an export call's arrival, -// counted from 1; see Steps. +// One event, who did it, and, for a host event the guest observes outside its calls, the step in +// which it happened; see Steps. record line { by: party, at: option, event: event } ``` -What the types don't enforce: - -- **Who acts.** `by` is the caller for `Call` and `Cancel`, the callee for `Started`, `Return` - and `Cancelled`, the host for `Write` and `Read`, the writer for `Value`, and whoever - dropped it for `Drop`. Most of it follows from the event; it is explicit, for readability. -- **Task ids.** A `TaskId` comes from a counter in the generated component that sees the call, - not from the runtime. wasmtime numbers its tasks internally, but no canonical built-in gives a - task's id to wasm, and other runtimes number tasks their own way. Each guest import call arrives - at the imports component as a new task, which nothing identifies but task-local storage - (`context.get`, `context.set`), which the callee fills itself. The subtask handles of the - imports component's own calls to the host are reused, and replay makes no host calls. Replay - numbers calls the same way, and they come in the same order, as replay checks each guest call. -- **Answers.** A sync call is answered by a `Return`; an async call by a `Return`, `Started` then - a `Return`, or `Started` then `Cancelled`, possibly with a `Cancel` in between. Other lines may - come between a call and its answer, e.g. host events of the other component, so the id pairs - them. A p2 component's calls are all sync, and each `Return` comes right after its `Call`. -- **Cancellation.** `Cancel` is a request: the caller's `subtask.cancel`, which the callee gets - as a cancellation request. The callee may acknowledge it, with `task.cancel`, which is - `Cancelled`, or return anyway, which is a `Return`, also when its result was already on its - way. A call that hasn't started yet is cancelled without the callee seeing it. It goes both - ways: the guest can cancel a pending import, and the host a pending export call. - - **The guest's cancel of an import** reaches the proxy as its export's cancellation, which - drops the call's wrapper, and wit-bindgen acknowledges it at once. So the host answers - `Cancelled`, without an `at`, as it answers a call; in record mode, the host's call is - cancelled with it, and a result it returned meanwhile is dropped, which the guest wouldn't - have read either, as it dropped the call. Only a STARTED call can be cancelled. Replay's mock - of a call the trace answers with `Cancelled` waits until the guest cancels it. - - **The host's cancel of an export** isn't recorded yet: the proxy would have to tell whether - the guest then returned or acknowledged it, which wit-bindgen doesn't say. -- **Cancel and Drop** are about different things: `Cancel` about a pending call, `Drop` about a - handle. - - **`Cancel`.** In wit-bindgen, an async import call is a Rust `Future`; dropping it while the - call is pending cancels the call, with `[subtask-cancel]`: a `Cancel` of the task. - - **`Drop`** ends either end of a stream or future, or a resource. Dropping a writer ends the - stream, which the reader sees as its read returning `dropped`; dropping a reader, the writer - sees as its next write returning `dropped`. The proxy sees each side's drop that way, from its - own end, and records it as a `Drop` by that side. - - **A pending copy can be cancelled too**, with `stream.cancel-read` or `cancel-write`, which - doesn't end the stream. It isn't recorded: a cancelled copy never completes, so there is no - copy line, and the other end doesn't see it. -- **Labels** are handles in the two generated components' tables, and tasks are numbered by the - two components' counters, so the `table` of a label or a `TaskId` is part of what it names. A - label names one handle, from its first line to the one that ends it; see [Labels](#labels), - also for the table of a label inside a value, which is written by its name alone. -- **Positions.** `at` is the step of a host line the guest observes outside its calls, including - an export call's arrival; other lines have none. See [Steps](#steps). -- **Copies.** A copy completes once, when both sides have acted, so it is one line. Copies are - lazy: if the guest never reads a stream the host writes, no `Write` appears. For `stream` a - line is the chunk one rendezvous copied, so replay can give the guest's reads the same chunks. - This holds for pumps that don't read ahead; [Read-ahead](#read-ahead-in-pumps) would change it. - The host's side waits until the other side is ready, with a zero-length write or read, which - completes once the other side reads or writes and leaves its copy pending, so that the copy - completes as the host issues it, and its line is recorded before the guest goes on. A future - has no such wait, so the host's value is recorded as it is written. Replay's jobs wait alike, - so that the same events wake the executor. -- **Order.** The trace is not a total order. Each line belongs to a task, by its `TaskId`, or to a - handle, by its `Label`, and the lines of different tasks or handles may come in any order. What - orders them is each task's own sequence, `Call`, `Started`, `Return`; the `at` of the host lines - the guest observes outside its calls; and the ids, which are numbered in the order calls arrive. -- **No guest task on import calls.** The component model passes no caller identity, so the proxy - can't tell which of the guest's own tasks made an import call: `c5` can't be linked to the - export call `t1` that caused it. That limits deriving other orders from a trace: - - **What the trace does order:** a `Return` comes after its `Call` and `Started`, a copy after - its stream's first appearance, a `Cancelled` after its `Cancel`. - - **What it doesn't:** which guest lines a host line caused. If the guest's `Call c3` comes after - the host's `Return c2`, it may be the same guest task, reacting to `c2`, or an unrelated one. - Only in the second case may `Return c2` move after `Call c3`. - - **So** a reordered trace is checked by replaying it. If the guest diverges, the order wasn't - consistent, or the guest behaves differently in it; either way, the trace says nothing after - that point, which p3.md calls holes. Exact dependencies need caller identity, which the - [core-level shim](#portability) could provide, from the guest's task-local `context.get`. - -An example, for `GET /wait?ms=250` on [`examples/wasi-http-p3`](../examples/wasi-http-p3), writing a -`TaskId` as `t1` or `c1`, and a `Label` by its name; every label here is in the imports table: - -| `by` | `at` | `event` | -|---|---|---| -| host | 1 | `Call { id: t1, is_async: true, func: "wasi:http/handler@0.3.2.handle", args: ["#request-1"] }` | -| guest | | `Call { id: c1, is_async: false, func: "wasi:http/types/request@0.3.2.get-method", args: ["#borrow-request-1"] }` | -| host | | `Return { id: c1, value: Some("get") }` | -| guest | | `Call { id: c2, is_async: true, func: "wasi:clocks/monotonic-clock@0.3.2.wait-for", args: ["250000000"] }` | -| host | | `Started { id: c2 }` | -| host | 4 | `Return { id: c2, value: None }` | -| guest | | `Call { id: c3, is_async: false, func: "wasi:http/types/response@0.3.2.new", args: ["#fields-2", "some(#stream-5)", "#future-6"] }` | -| host | | `Return { id: c3, value: Some("(#response-3, #future-7)") }` | -| guest | | `Return { id: t1, value: Some("ok(#response-3)") }` | -| host | 6 | `Read { stream: #stream-5, items: "[97, 115, 107, …]" }` | -| guest | | `Drop { handle: #stream-5 }` | -| guest | | `Drop { handle: #future-7 }` | - -The lines about the response body, `#stream-5`, which the guest passed to `response.new`, come -after the export's `Return`: the host's `Read` of the body, and the guest's `Drop` of it, which ends -it. The guest returns the response before it writes the body, in a spawned task. The last line is -the guest dropping `#future-7`, which `response.new` returned. +The lines of a call (`call`, `started`, `return`, `cancel`, `cancelled`) belong to its task, and +those of a stream or future (`write`, `read`, `value`, `drop`) to its label. Lines of different +tasks or handles may come in any order: what orders them is each task's and handle's own sequence, +and the steps. + +An example, for `GET /wait?ms=250` on a wasi-http handler, in the text form, where `<--` puts +data into a stream or future and `-->` takes it out: + +``` +host @1: t1.call async wasi:http/handler.handle(#request-1) +guest: c1.call wasi:http/types/request.get-method(#borrow-request-1) +host: c1.return get +guest: c2.call async wasi:clocks/monotonic-clock.wait-for(250000000) +host: c2.started +host @4: c2.return () +guest: c3.call wasi:http/types/response.new(#fields-2, some(#stream-5), #future-6) +host: c3.return (#response-3, #future-7) +guest: t1.return ok(#response-3) +host @6: #stream-5 --> [97, 115, 107, …] +guest: drop #stream-5 +``` + +The guest returns the response before it writes its body, which the host then reads. + +Some properties the types don't show: + +- **Copies.** A copy completes once both sides have acted, so it is one line, the items one + rendezvous moved; copies are lazy, so a stream nobody reads has no `write`. The host's side waits + until the other side is ready, with a zero-length read or write, so that each copy completes as + the host issues it, and is recorded before the guest goes on. +- **Futures.** A future can't wait for its reader, so the host's value is recorded as the host + writes it; the guest may drop its end instead, which isn't recorded. The guest's value is + recorded when the host takes it. +- **Cancellation.** The guest cancels an import by dropping its pending call. The proxy + acknowledges it at once, so the trace has the guest's `cancel` and the host's `cancelled`; in + record mode the host's call is cancelled with it. Only a STARTED call can be cancelled. +- **No caller identity.** The component model doesn't say which of the guest's tasks made an + import call, so a trace can't tell which guest lines a host line caused, only their order. ## Labels -A label names a handle by its table and its name, as WAVE writes it: its type and the handle's -number in that table, e.g. `#fields-3` or `#stream-7`. - -- **Resources** of the host are in the imports table, which holds them for both components. In - record mode, a host resource is labeled by the handle the imports component keeps for it, which - the exports component gets through `get-handle-*`; in fuzz and dialog modes, a mock by a number - the imports component gives it when it makes it, counting from 1, which no other mock gets; in - replay, a mock carries the trace's label. The mock's own handle can't label it: it moves on with - the mock, which frees its number, and a borrow the guest passes back comes in as the mock - itself, without a handle. Fuzz mode records the end of every mock, also the host's drops, which - record mode can't see; they take no step, as the guest doesn't see them, and replay has nothing - to do for them. A resource of the guest, which the exports component wraps, is in the - exports table, e.g. the host's drop of a session, `drop #session-1 (exports)`. -- **Streams and futures** are labeled by the handle of the end the proxy received, which it keeps - for the stream's lifetime: the host's reader in an import's result or an export's arguments, the - guest's in an import's arguments or an export's result. The proxy pumps it through a pair of its - own, whose writer never appears in the trace. A stream or future first appears in the value that - brings it across, in the table of the component that call or result crosses, and every later - line uses the same label: - - | `by` | `at` | `event` | - |---|---|---| - | guest | | `Call { id: c4, is_async: false, func: "test:p3/host/body@0.1.0.consume", args: ["#body-1"] }` | - | host | | `Return { id: c4, value: Some("(#stream-6, #future-9)") }` | - | host | 5 | `Write { stream: #stream-6, items: "[52]" }` | - - - **Record mode** prints a call's arguments converted, and its result as received, as it does - for every value. So the reader it hands on in the arguments is printed by the received end's - label too, which it registers with wasm-wave's `set_handle_label`, as fuzz mode does. - - **Fuzz and dialog modes** make the stream themselves: they keep the writer, and hand the - reader out, which frees the reader's slot. So they label the stream by the writer, also where - the reader is printed, in the value that hands it out: they register the pair with wasm-wave's - `set_handle_label`, which prints the reader under its writer's number until the reader has - been printed. Pumping it through a pair the mock keeps the reader of, as record mode does, - isn't possible: wasmtime traps a copy between two ends of one component instance, unless - the payload is numeric. - - **Replay** creates a pair for each stream the host hands out, hands out its reader, and - performs the trace's lines on the stream's label through its writer. - - **The guest's `stream.new` and `future.new`** are built-ins inside its component that the - proxy doesn't see, so a stream the guest creates first appears where it crosses. Replay doesn't - need its creation, as the guest creates it again itself. -- **Two tables, neither the main component's.** The imports component keeps the ends that cross - at the main component's imports, the exports component those of its exports, and the two can - hold the same number. So a label carries its `table`. Neither table matches the main - component's handle ids, even in p2, as the main component's table also holds handles the proxy - never sees: its own resources and streams, waitable sets and subtasks. Labels only need to be - consistent: replay treats them as names, and a mock carries the trace's. - -Limits: - -- **An end that comes back, or passes through the guest, gets a new label.** When the guest hands - back a stream the proxy gave it, e.g. an `echo` that returns its argument, or passes a stream - from an export on to an import, the proxy gets a new handle, and nothing in the component model - tells it that this is the stream it already labeled. The trace stays consistent, and replays: a - reader of it just doesn't see the two labels as one stream. The [core-level shim](#portability) - could carry the label through, as the end keeps one handle in the guest's table from when it - arrives until the guest passes it on. -- **An end the proxy gave the guest, coming back, can't be copied from, unless its payload is - numeric.** The end is the reader of a pair the proxy made, e.g. a pump's, or a mock's in fuzz - and replay modes, whose writer the proxy keeps. Reading it in the proxy copies between two ends - of one component instance, which wasmtime traps for a payload that isn't numeric ("cannot read - from and write to intra-component future/stream with non-numeric payload"), as the component - model requires. So recording a guest that hands such a future or stream back traps, e.g. an - HTTP echo that passes a request's trailers, a `future, error-code>>`, - on to its response; a `stream`, e.g. the body, is copied. The proxy can't tell that the end - is its own, as it comes back as a new handle. Pairs made in another component instance would - avoid the trap, at the cost of a function per payload type to make them and pump them there. -- **Replay compares labels up to renaming.** A table holds more than the values the trace - labels: waitable sets and subtasks, which each mode makes its own, so the same value can have - another number in replay. Replay pairs each of the trace's labels with the one it sees for the - same value, one to one, per table, from the first guest line that has both, and requires every - later line to agree. A label names a handle until the handle leaves the table, which frees its - number: at a `Drop`, a future's `Value`, or when the host hands it on, owned, to the guest, in - an export call's arguments or an import's result; the rule is the same for every label. After - that, the number may name another handle, as a variable may be bound again. So the recorder - names each binding apart, as it records each line, from the lines before it alone, which a - run that traps leaves too: the first keeps the number, e.g. `#stream-7`, and the next are - `#stream-7-2`, `#stream-7-3`, and so on. A trace's label then names one handle, whatever order - replay's jobs process the lines in. A host line can come after the guest acted on a handle the - line brings in, as the host records a copy once its job sees it complete: e.g. a future's value - with a mock in it, which the guest took and dropped first. So a `Drop` or a `Value` of a label - no line has mentioned yet ends the binding of the line that brings it in. Replay's labels are - its own numbers, which it reuses too: a replay label is paired again once the trace's label it - was paired with has ended. A stream or - future replay makes, from a value the trace hands out, first appears in a host line, which - replay doesn't check, so replay pairs its label explicitly, with the binding after that line: - the end the host keeps. The lines replay performs it renames to its own labels, e.g. the - guest's resource a host `Drop` names. So replay checks that the labels are used consistently, - not their numbers. For that, the trace has the end of every handle: fuzz mode records the - host's drops of error-contexts too. -- `list` items are WAVE lists for now; p3.md's byte strings (`b"…"`) would be shorter. +A label names a handle in one of the two generated components' tables, by its type and number, +e.g. `#fields-3`. Neither table is the guest's, which also holds handles the proxy never sees, +and the two can hold the same number, so a label carries its table. Labels only need to be +consistent: replay treats them as names. + +- **Host resources** are labeled by the host's handle in record mode, and in the other modes by a + number the mock gets when it is made. +- **Streams and futures** are labeled by the end the proxy holds for the stream's lifetime: the + end it received, or, for one it made, its writer. A stream first appears in the value that + brings it across. + +**Bindings.** A number names a handle until the handle leaves the table: at a `drop`, a future's +`value`, or when the host hands it on, owned, to the guest. The number can then name another +handle, as a variable can be bound again. So the recorder names each binding apart as it records +each line, from the earlier lines alone: the first keeps the number, e.g. `#stream-7`, the next +are `#stream-7-2`, `#stream-7-3`, and so on. A label then names one handle, and a trace cut short +names its bindings as the whole trace would. A guest's drop can be recorded before the host's +line that brought the handle in, as the host records a copy once it sees it complete, so an end +of a label no line has mentioned belongs to the binding that line starts. + +**Renaming.** Replay's handle numbers differ from the recording's, as each mode makes handles of +its own. Replay pairs each trace label with its own label for the same value, one to one, from +the first line that has both, and requires every later line to agree; a replay label is paired +again once the trace's label has ended. A stream replay makes itself, from a value it hands out, +is paired explicitly. So replay checks that labels are used consistently, not their numbers. + +**Ends that come back.** When the guest hands back a stream or future the proxy gave it, the +proxy gets a new handle, and nothing tells it that the end is its own: + +- It gets a new label: the trace stays consistent, but doesn't show the two as one. +- It can't be read by the proxy, if its payload isn't numeric: that would copy between two ends + of one instance, which the component model forbids, and wasmtime traps. E.g. an HTTP echo that + passes a request's trailers future on to its response can't be recorded. Making the proxy's + pairs in another component instance would avoid it. ## Recording -Recording runs the composition in the real host, as for p2: the imports component forwards to -the host and records, and the exports component records the calls the host makes into the main -component. - -### The executor delivers and records host events - -In record mode, the imports component routes every interaction with the host through one -executor task, like fuzz mode's: - -- **Async imports.** The proxy's export polls the host's call once: if the result is ready, it - returns it at once. Otherwise it records `Started`, and hands the call to an executor job, - which awaits the host's subtask and records the `Return` in the step of the run it completed - in, then gives the result back to the proxy's export, which returns it (its `task.return`). It - waits for the job as replay's mock waits for the step, holding the executor until it resumes - (see [Steps](#steps)). The job may borrow the guest's arguments, e.g. a method's `self`: if the - guest cancels the call, the job is dropped with it, which cancels the host's call too. -- **Running the executor.** There is no `start` in record mode, so the exports component runs - the executor while the host's calls of async exports are pending, and stops it when the last - one returns, which lets it finish its jobs; a call that comes meanwhile keeps it running. -- **Streams and futures, by pumps.** The proxy doesn't pass a stream end through, as the copies - would then go between the host and the guest without it. It keeps the end it received, creates - a pair of its own, and passes on that pair's other end. A pump is the executor job between the - two: it reads from one end, writes what it read to the other, and records each copy, and passes - on a drop the same way. It reads from the writer only once the reader reads, without - [read-ahead](#read-ahead-in-pumps): a zero-length write to the reader's end waits for that, as - it completes once the reader reads, and leaves the read pending. A future is pumped once, for - its value. - -So nothing the host does reaches the guest outside an executor run. That's why a step number can -mark when each host event happened. - -Putting the executor between the host and the guest must not change the run it records, which -these rules keep it from doing: - -- **Delivery on arrival.** The executor delivers each host result as soon as it arrives, without - holding or yielding, so that several results can be pending for the guest at once, as without - the proxy. -- **RETURNED is preserved.** When the host's result is ready on the first poll, the proxy's - export returns before it blocks, so the guest sees RETURNED; otherwise STARTED. Replay - reproduces it without a host: the mock of a call recorded RETURNED returns before it awaits, and - one recorded STARTED awaits its step first. This needs the host to be the runtime's own - functions, as it is under `wasmtime serve` or `run`; see [Spike results](#spike-results) for a - host that is a component. -- **No blocking WASI 0.2 calls during a run.** A sync WASI 0.2 function such as - `blocking-write-and-flush`, which `println!` calls, can suspend the instance in the middle of the - call, while it stays entered: other tasks run, and calls into the instance wait, which changes - the schedule, and which the proxy can't record. So the proxy, the bundled recorder, which today - prints each line as it is recorded, and the debug component, whose prints fuzz mode and a - guest's debug builds use, must buffer their output and write it once the run is over, as the - spike does, or leave the trace to a host recorder. A buffer is lost if the run traps, and a run - that doesn't end, e.g. under `wasmtime serve`, has no end to write it at. -- **No epoch deadlines that yield.** wasmtime's epoch interruption ends a guest's time slice at a - point set by a timer thread, wherever the guest is. - - **A deadline that traps**, as `wasmtime -W timeout=…` sets, only ends the run, and the trace - with it. A run that finished before its deadline replays; one that hit it stops at a point - replay has no timer for, so replay goes on past the end of the trace. - - **A deadline that yields** (`epoch_deadline_async_yield_and_update`), as embedders use to - time-slice guests, suspends the guest in the middle of its computation, while its instance - stays entered. The executor can then run a step, and deliver a host event H, before the - guest's next wait W. Replay can't interrupt the guest, so it runs that step only once the - guest waits at W. Step numbers stay the same; what moves is where the step falls relative to - the guest's computation. The guest sees the difference when another event E is pending at W: - in record, H and E are both pending, and the runtime returns the lower waitable, say H; in - replay, only E is. - - A p2 guest isn't affected: it is sequential, and sees the host only through calls, so pausing it - changes nothing it can observe. [The shim](#portability) would lift this requirement. - -### Steps - -The imports component's executor is a single task that runs the proxy's jobs: the host's side of -its pending async imports, streams and futures (see [`assets/executor.rs`](../assets/executor.rs)). -It runs whenever the runtime polls its task: when it is started, and each time it is woken, by -new work being queued or by a host completion one of its jobs waits for. Each run is a **step**, -and so is each arrival of an export call, below. Steps are counted from 1, by one counter in the -imports component, and in each run the executor polls all its jobs. - -Replay has to know when to perform each host line. There are three kinds of lines: - -1. **Guest lines**, `by: guest`: the guest's calls, its export results, its drops. Replay doesn't - perform them: they happen when the guest does them, and replay checks each against the trace. - They have no `at`. -2. **Host lines that answer a guest call before it returns**: the `Return` of a sync call, or of a - RETURNED async one, and `Started`. The proxy did them while handling the call, before the call - returned to the guest, and replay does too: when the guest makes the call, replay performs them - at once, as part of answering it. The guest's `Call` line says when they happen, so they have no - `at`. -3. **Host lines the guest observes outside its calls**: what the host does on its own schedule, - rather than in answering a call. Mostly, the host returns something to the guest: the `Return` of - a STARTED call, the items of a `Write`, a future's `Value`. Also the host taking what the guest - wrote, a `Read`, which completes the guest's write; the host dropping its end, a `Drop`, which - the guest sees as `dropped`; a `Cancelled`; and the host calling an export, below. The executor - performs them when the host gets to them, so `at: n` says they happened in step `n`. For a copy, - it is the step in which the host made its side, e.g. issued the write the guest then read, - rather than the one in which the host saw it complete, which may come after the guest reacted - to it. In replay, the executor counts its steps the same way, and a job holding such a line - waits until the count reaches `n`. There is no host to wake the executor, so a job waiting for - a later step makes the executor wake itself, by yielding, and its next run is the next step. - That run first takes an event that came while it yielded, e.g. a guest's write, as a waiting - executor woken by the event does in the recording: wit-bindgen would only take it after a - poll, in another run. So the executor's callback, which wraps wit-bindgen's, makes the first - poll after a yield a probe, which only lets wit-bindgen take a pending event. - A task that waits for a step from outside the executor, e.g. the mock of a STARTED call, holds - the executor from the run it waited for until the task resumes: otherwise a run, e.g. for a - guest's drop, could come in between, which replay, knowing only the step, would place - differently, as it waits by waking the executor itself, which changes the order in which - wasmtime delivers the guest's events. - -**Export calls** arrive at the exports component, from the host, so their arrival is a step of its -own. On each arrival, the exports component calls `step` on `proxy:conversion/executor`, a sync -function that increments the counter and returns it, as the export `Call`'s `at`; the executor's -later runs are numbered after it. In replay, `start` makes an export call recorded at step `n` once -the count reaches `n - 1`, by calling `step` and then the export. The wait and the step are one -call, `step-after`, which takes the step in the imports component's task as soon as its wait -resumes, while the executor is still held, as above. As two calls, the executor could run between -the wait's return and `start`'s call of `step`. An arrival can't fall within a run of the -executor: the host only calls an export while no wasm is running, unless something suspends where -the proxy can't see it, which the requirements below rule out. The host's drops of the guest's -resources, which `start` makes too, take a step the same way. - -`start` calls only the main component's exports, not the host's own functions, as the real host -doesn't call those through the proxy. A call like `consume` of a body the host made would put -both ends of its streams in the generated components, one in each: copies the guest never sees, -which the imports component's executor can't take over, as wasmtime traps a copy between two -ends of one component unless the payload is numeric. - -The exports component's jobs, e.g. reading a stream an export returned, run on the task of -`start`, not in the executor: a line of theirs has the last step, and replay's job waits until -the count is there. That places them less exactly than the executor's runs do; see open -question 8. - -### Why the step is enough, and a gate isn't needed - -The trace order alone is not a replayable schedule. Say the guest has two `fetch` subtasks in one -waitable set, and is busy: - -1. The host finishes `b`, then `a`. The proxy records them in that order, and delivers both. -2. The guest then waits, finds both pending, and wasmtime returns the lowest waitable: `a`. -3. A replay that releases each event as soon as the trace cursor reaches it may release `b` while - the guest is waiting, so the guest takes `b` first: a different run. - -component-ui's `docs/p3.md` fixes this with a gate: hold every host result, and release one only -when the guest is idle. That excludes schedules: a guest never finds two results ready at once, -and every held call turns from RETURNED to STARTED. It also needs to know when the guest is idle, -which the proxy can't see. - -The step fixes it without holding anything. Every host event reaches the guest through the -executor, in one of its steps. If the runtime's choices are deterministic, everything that happens -between two steps follows from what happened before, so "host event H was performed in step n" is -a position that means the same in record and replay. Replay performs H in step n, -exactly where it was delivered, and the guest's waits fall in the same places. In the example, -both results are delivered before the guest waits, in record and in replay alike. - -From [the spike](#spike-results), with `fetch("b")`'s timer firing before `fetch("a")`'s. The -spike doesn't count its export call as a step: - -| `by` | `at` | `event` | | -|---|---|---|---| -| guest | | `Call { id: c1, …, func: "fetch", args: ["a"] }` | | -| host | | `Started { id: c1 }` | | -| guest | | `Call { id: c2, …, func: "fetch", args: ["b"] }` | | -| host | | `Started { id: c2 }` | steps 1 and 2: the executor's first run, and its run for the queued calls | -| host | 3 | `Return { id: c2, value: Some("B") }` | step 3: `b`'s completion woke the executor | -| host | 4 | `Return { id: c1, value: Some("A") }` | step 4: `a`'s completion woke it again | - -The guest returns `"BA"`. In replay there is no host: the mock of each call waits until the -executor's count reaches its `at`, and the executor, with a job waiting, yields, and comes back as -its next step. So `b`'s result is released in step 3 and `a`'s in step 4, and the guest's waits -receive them in the same order, with the same results in each. Released only by their order in the -trace, the two results could reach the guest before or after a different one of its waits. - -[The spike](#spike-results) confirmed this in wasmtime. It requires: - -- **Deterministic runtime choices**, see [Portability](#portability). -- **The same executor in record and replay**, with only the leaves different: host calls in - record, futures that the trace makes ready in replay. Its work in a step depends only on its - state and the trace's lines for that step. The same goes for `start`: fuzz mode's waits for - the steps it chooses before an export call, through `step-after`, as replay's waits for the - trace's, so that the two make the same tasks. -- **Steps in replay where the host woke the executor in the recording.** Replay has no host, so a - job that waits for a later step makes the executor yield, and come back as the next step. - wasmtime runs the ready tasks first, as it did before the host's completion in the recording. -- **No suspension the proxy can't see**: no calls to other components where the recording calls - the host, no blocking WASI 0.2 calls, and no epoch deadlines that yield, as above. -- **One clock for both components.** The exports component takes its steps from the same - counter, through `step` and `step-after`, as [Steps](#steps) describes. - -### What recording can't reach - -The proxy puts a hop, its own task, between the host and the guest. With delivery on arrival: +Recording runs the composition in the real host. The imports component forwards every +interaction with the host through its executor: + +- **Async imports.** The proxy polls the host's call once: if the result is ready, it returns it + at once, so the guest sees RETURNED. Otherwise the call is STARTED, and an executor job awaits + the host's subtask, and records its `return` in the step it completed in. +- **Streams and futures** pass through pumps. The proxy keeps the end it receives, makes a pair of + its own, and hands on the pair's other end; an executor job copies between the two, records each + copy and drop, and reads from the writer only once the reader reads, without read-ahead. +- **The executor runs** while the host's calls of the guest's exports are pending, as there is no + `start` in record mode, and finishes its jobs when the last one returns. + +So nothing the host does reaches the guest outside a run of the executor, which is what lets a step +mark when each host event happened. Putting the executor in between must not change the run, which +these rules keep: + +- **Delivery on arrival.** The executor passes each host result on as soon as it arrives. +- **RETURNED is preserved**, as above. This needs the host to be the runtime's own functions: a + host that is another component suspends the proxy as it calls it, so that the guest sees + STARTED, in a way replay can't reproduce. +- **Nothing suspends where the proxy can't see it**: no blocking WASI 0.2 calls in the proxy or the + recorder, e.g. printing, and no epoch deadlines that yield, which pause the guest mid-computation + at a point replay has no timer for. A deadline that traps only ends the run. + +## Steps + +The executor is a single task that runs the proxy's jobs. Each of its runs is a **step**, and so is +each arrival of an export call; one counter, from 1, numbers them in both components. A host line +the guest observes outside its own calls, e.g. a STARTED call's `return`, a copy, a host's drop, an +export call, has `at`: the step it happened in. A guest line, or a host line that answers a guest +call before it returns, e.g. a `return` of a RETURNED call, has none: the guest's call places it. + +**Replay** counts its steps the same way, and performs each host line in its step. With no host to +wake the executor, a job waiting for a later step makes the executor wake itself; the next run +first takes an event that came meanwhile, e.g. a guest's write, as an executor woken by that event +does in the recording (see [wit-bindgen](#wit-bindgen)). A task that waits for a step from outside +the executor, e.g. a mock of a STARTED call, or an export call waiting to arrive, holds the +executor until it resumes: otherwise another run could come in between, which replay, knowing only +the step, would place differently. + +**Why the step is enough, and a gate isn't needed.** The trace order alone isn't a schedule: +released in trace order, two results could reach the guest before or after a different one of its +waits. component-ui's design holds every result until the guest is idle, which excludes schedules, +turns every RETURNED call into a STARTED one, and needs to know when the guest is idle, which the +proxy can't see. Instead, every host event reaches the guest in a step; if the runtime's choices +are deterministic, everything between two steps follows from what happened before, so "event H in +step n" means the same in record and replay. This requires: + +- the same executor in record and replay, with only the leaves different: host calls, or the + trace's lines; +- deterministic runtime choices, given the same state; +- nothing suspending where the proxy can't see it, as above; +- at most one event pending for the guest at once, see below. + +A spike, [`tests/fixtures/spike-p3-race`](../tests/fixtures/spike-p3-race), confirmed the clock in +wasmtime, with a guest that joins two results in the order its wakeups receive them. + +### Answers in the same step + +Steps place each host event, but not the order of two events that reach the guest in one step, +e.g. two calls returning in the same run of the executor. When both are pending at one of the +guest's waits, wasmtime gives the guest the lowest waitable first, by its index in a table the +whole store shares, not by the guest's own handle. That index depends on every task and handle +the store made before, also the proxy's and the recorder's, which differ between record and replay +by design. So the guest may take the other event first, while every step agrees, and diverge, +e.g. a `select` of two such calls. + +The proxy doesn't see which event the guest took, so the trace can't record it; see +[Ordering answers in the same step](#ordering-answers-in-the-same-step) for ways to make it +replayable. + +## What recording can't reach + +The proxy puts its own task between the host and the guest, and observes only its side: | | | |---|---| -| RETURNED | preserved, when the proxy forwards to host functions rather than another component | -| several host results pending at one guest wait | possible, as nothing holds them back. A wit-bindgen guest still takes them one per wakeup, the lowest waitable first, with or without the proxy: its callback receives one event at a time, and its futures don't see the others until their own events | -| a new export call | preserved: forwarded in the same task | -| a guest read or write that completes at once, as the host had data or was reading | only if the pump [reads ahead](#read-ahead-in-pumps); otherwise it blocks first | -| a host result arriving before the guest's next wait | may arrive after it instead, as the executor needs a turn to pass it on: a slower host, which is legal. [Read-ahead](#read-ahead-in-pumps) doesn't help: an async import's result has nothing to read ahead, and a stream's data that arrives just before the wait still waits for the pump. A wit-bindgen guest, whose callback gets one event per wakeup, can't tell the difference when this is the only event, but can when another is pending at the same wait: without the proxy, the lowest waitable of the two comes first, with it, the other one. A guest that calls `waitable-set.poll` also finds nothing pending | +| RETURNED | preserved, with the runtime's own host functions | +| several results pending at one guest wait | possible, but which the guest takes first isn't replayed; see above | +| a guest copy that completes at once | not reached without read-ahead: the guest's copies block first | +| a host result arriving before the guest's next wait | may arrive after it, as the executor needs a run to pass it on: a slower host, which is legal | +| the last copies before a trap | the guest's drops, and the values of futures it wrote, which the host sees only after the guest went on, may be missing: see below | +| an end the guest hands back | traps, unless its payload is numeric; see [Labels](#labels) | +| the host's cancel of an export | not recorded yet: the proxy would have to see whether the guest returned or acknowledged it | + +Whatever schedule happens is recorded completely; the proxy only makes the host look slower. +Covering other schedules is exploration, which fuzz mode does. + +**Traces cut short.** A run that traps, or is killed, during an export call leaves a trace whose +last guest copies may be missing. Replay treats a trace with an unanswered export call as cut: it +accepts a guest's line about a stream or future that the trace lacks, and lets the run go on to +the same trap. The data the guest got, and the host took, is always recorded, as the host's side +of a copy completes it. + +**Read-ahead** would make a guest's immediate copies reachable: a pump that keeps a read pending on +one side has data ready for the other. But the host is then read earlier, and more, than the guest +asked, which changes what it sees, e.g. a producer that produces only when read, and a copy line +is no longer one rendezvous of the guest's. Record mode doesn't read ahead. -A run that traps, or is killed, during an export call cuts its trace short: +## Replay -| | | -|---|---| -| the guest's last copies before a trap: its drop of a stream or future, or the value of a future it wrote | may be missing, as the host records them once it sees them complete, after the guest went on, and the guest may trap first. A trace with an export call that has no answer is cut: replay accepts a guest's line about a stream or future that the trace lacks, without checking it, and takes what the guest writes since, so that it goes on to the same trap. The host's own copies, and the guest's writes to its streams, are recorded as the host completes them, so a cut trace has every value and item the guest got, and every item the host took. A trap after every export call returned, e.g. in a task the guest spawned, leaves a trace that looks whole, whose replay then reports the missing line as a divergence | +Replay runs the composition with no host. The imports component answers every import from the +trace, the exports component's `start` makes the export calls, and the executor performs each host +line in its step. Every guest line is checked against the trace, up to renaming; a mismatch is a +divergence, reported with the expected and the actual line, and a stall shows up as a deadlock. +At the end, replay checks that it reached every line. + +The recorder, a component bundled with the proxy or implemented by the host, reads and writes the +trace, and pairs labels; waiting for steps is the executor's. Fuzz mode writes the same format, so +that its runs replay too. + +## Correctness + +Replay is sound if every run that recording sees replays exactly; coverage, recording every +schedule the composition could have, isn't a goal (see +[What recording can't reach](#what-recording-cant-reach)). + +**Soundness, by induction on steps.** If record and replay are in the same state when step `n` +begins, and step `n` performs the same host lines, they are in the same state when step `n + 1` +begins; between steps, the guest's lines are checked, and its calls answered by the lines without +an `at`. This holds if the executor is the only way in, the trace holds every input, and the +requirements of [Steps](#steps) hold. The trace doesn't hold one input wasmtime's choices depend +on, the store-wide order of pending events, so the induction holds only while the guest finds at +most one. + +**Fork-join guests**, without determinacy races, give the same result in every schedule, so +recording their values would be enough. A component model guest isn't one in general: it can +observe the order completions arrive in, as a `select` does, which is what steps record. -Whatever schedule happens is recorded completely. Not every timing the unproxied component could -have is reachable while recording, as the proxy only makes the host look slower, or, with -read-ahead, eagerer. Covering other schedules is exploration, which fuzz mode does. +**Checking the implementation:** -Only removing the hop would close the gap of a host result arriving late: the proxy would pass the -host's subtasks and stream ends straight through, and see nothing of them, so recording would have -to observe from inside the guest, as the [core-level shim](#portability) does, which sees what each -of the guest's waits returns. Replay would still mock the imports in the proxy. +- Replay checks itself, for each trace: a wrong replay shows up as a divergence or a stall. +- Fuzzed schedules are replayed: fuzz mode explores the host's timing, and each trace has to + replay, or end in the same trap of the guest's. +- Recordings under `wasmtime run` and `wasmtime serve` are replayed. +- Further: a fixpoint test, replaying while recording and comparing the traces; enumerating every + placement of completions for small guests; and a model of the spec, whose reference + implementation marks its nondeterministic choices, to check the design apart from wasmtime. + +## Portability + +Replay in the runtime that recorded needs the same runtime version and configuration, and the +proxy, which wasmtime is deterministic enough for, but for +[answers in the same step](#answers-in-the-same-step). Replay in another runtime needs the +runtime's choices to be the same in both. Two ways: + +1. **A deterministic scheduling profile** in the component model, as core Wasm has for NaNs: fix + each choice the spec leaves open, e.g. the lowest waitable first, by the waiting instance's own + handle, the oldest ready thread first, and when a host completion is delivered relative to + ready tasks. It fixes the choices rather than recording them; runtimes would adopt it behind a + flag, as they do async. +2. **A shim inside the guest**: rewrite the guest component to put a core module between its core + instance and its async built-ins, which reports what each wait returned to the proxy. Replay + then releases exactly what each recorded wait saw, also when the guest is suspended where the + proxy can't see it. It observes the built-ins, and doesn't reimplement them. + +Patching the runtime would also work, but replay must not depend on runtime patches. + +## Status + +1. **Trace format and recorder interface**: done. +2. **Replay**, tested on fuzzed traces: done. +3. **Record**: async imports, streams and futures, and the guest's cancels, under `wasmtime run` + and `wasmtime serve`: done. +4. **Portability across runtimes**: the profile, or the shim. + +## Open problems + +### Ordering answers in the same step + +When two events reach the guest in one step, which it takes first depends on wasmtime's store-wide +indexes, which differ between record and replay (see +[Answers in the same step](#answers-in-the-same-step)). Fuzzing a guest that races two calls, +keeping the first, diverges in about 3 of 100 runs; any guest that merges results, e.g. with +`select`, is exposed, in record mode too, when two host results arrive before the executor's next +run. + +- **One answer per run.** The executor releases at most one event for the guest per run, and wakes + itself for the next; wasmtime then runs the guest, at high priority, before the executor's next + run, so the guest never finds two of the proxy's events pending, and steps order them. It's an + executor change: a release queue, and a rule for which event goes first, e.g. the trace's order. + The cost: recording can't reach several results pending at one wait, which a guest that drains + them would notice, and the second result reaches the guest a run later. +- **The shim** records what each of the guest's waits returned, which makes the order exact, at + the cost of rewriting the guest (see [Portability](#portability)). +- **A deterministic profile** ordering pending events by the waiting instance's own handles would + make wasmtime's choice a function of the guest's own history, and need nothing recorded. +- **Making the store's history the same** in record and replay isn't practical: the leaves differ, + host calls against the trace's lines, and the recorder is called differently. + +### Ends the guest hands back + +A stream or future the proxy gave the guest, coming back, has its writer in the same instance, so +the proxy can't read it unless its payload is numeric (see [Labels](#labels)). Passing a request's +body and trailers on to a response, common in HTTP handlers, traps the recording. Options: + +- **Pairs in another instance.** If a separate component instance made the pairs and wrote to them, + a returning end would be read across instances. It needs a function per payload type, to make a + pair and pump into it there, and the steps of that instance's copies would be placed only as + exactly as the exports component's jobs are (see below). +- **Recognizing the end.** If the proxy knew a returning end was its own, it could pass it on + rather than read it. Nothing in the component model identifies an end across components, and + handle numbers are reused; it would need a runtime or spec addition. + +### The exports component's jobs + +The exports component's own jobs, e.g. reading a stream an export returned, run on the task of +`start`, not in the executor, so their lines have the last step rather than their own, and replay +places them only by waiting until the count is there. Everything replays so far. Giving each copy +a step of its own made it worse, as two tasks then took steps in an order the runtime decides. +Handing these ends to the imports component's executor, with a function per payload type, would +place them exactly. + +### The host's cancels of exports + +The host may cancel a pending export call, e.g. `wasmtime serve` when a client goes away. The proxy +sees the cancel, but not whether the guest then returned or acknowledged it, as wit-bindgen hides +the callee's choice; and replay's `start` would have to cancel the call in its step. Not recorded +yet. + +### Traces cut short + +A trace is cut when the run trapped or was killed during an export call; then the guest's last +drops, and the values of futures it wrote, may be missing, and replay accepts their absence. A +trap after every export call returned, e.g. in a task the guest spawned, leaves a trace that looks +whole, whose replay reports the missing line as a divergence. Recording the run's end, e.g. by the +host, would tell the two apart. + +### The recorder's output + +The bundled recorder prints each line as it records it, a blocking WASI 0.2 call, which can +suspend an instance where the proxy can't see it. No test has shown a divergence from it, but +nothing rules it out. Buffering to the end loses the trace on a trap, and a run under +`wasmtime serve` doesn't end; a host recorder avoids both, but needs a host that implements it; +writing through a WASI p3 stream from the executor would make output part of the steps. ### Read-ahead in pumps -Whether a pump reads from one side before the other side asks is a choice of record mode's -pumps, and fuzz mode's, not of replay's. Replay has to make the same choice as the recording, as -it runs the same executor, and the guest sees the difference: whether its copy completes at once -or blocks first. - -- **Without read-ahead**, the pump reads from the host only once the guest reads, and reads from - the guest only once the host reads. Each of the guest's copies blocks first, so a guest copy - that completes at once is never recorded, nor is whatever the guest does differently after one. - The two copies of a chunk, between the host and the pump, and between the pump and the guest, - happen together, so one line describes both. This is record mode's choice: - - A zero-length write to the reader's end tells the pump that the reader reads, but not how - much: the pump reads up to its own capacity, 4096 items, and the reader's reads may split the - chunk, which replay's writes split alike. - - A host's end may answer a zero-length copy at once, without being ready, as the component - model allows it; wasmtime's do. The pump then takes a chunk of the guest's as soon as the - guest writes it, which a host that isn't ready, e.g. under backpressure, wouldn't have. -- **With read-ahead**, the pump keeps a read pending on the host's side, and data ready for the - guest, or the other way, for a stream the guest writes. Immediate copies become reachable, and - every copy the guest makes is still recorded. What changes is what the host sees: it is read - earlier, and possibly more, than the guest asked. - - Items the pump read ahead, but the guest never took, were written by the host, though no - guest read took them. - - If the guest drops its reader after `k` items, the host's next write returns `dropped` - without the proxy, but succeeds into the pump with it, and the host learns of the drop later. - - A host whose behavior depends on being read, e.g. a network body consumed earlier, or a - producer that produces only when read (wasmtime's `StreamProducer::poll_produce`), may then - behave in a way the unproxied composition wouldn't. - - Replay stays consistent, as it reproduces the recording, not the unproxied run; the recording - is skewed on the host's side. -- **Read-ahead changes what a copy line records.** The two copies of a chunk now happen at - different times. For the guest's read to complete at once in replay, replay's pump has to have - the data in the step record mode's pump got it from the host, so a `Write` is the host's chunk, - with the step it arrived in, and the guest's reads may split it, which replay derives again. A - `Read`, likewise, is the host's take from the pump, in the step it happened, which frees the - pump to take more from the guest; the guest's writes are checked as they happen. Copies are then - no longer lazy, and a line is no longer one rendezvous of the guest's. - -### Hosts - -- Recording a wasi-http component under `wasmtime serve -S p3,http,cli --no-logging-prefix` - needs no change: `cli` for the WASI 0.2 interfaces a component built for `wasm32-wasip2` also - imports, and no prefix so that the output is the trace. wasmtime reuses an instance for several - requests of a p3 component, so a trace has an export call for each of them, which replay - makes in turn; an instance's trace is one recording. Recording `tests/wasi_http_p3.wasm` this - way replays, for each of its routes but its echo (see [Labels](#labels)). Viceroy is for p2 - components. -- Recording under `proxy-component run` passes the main component's p3 imports through to the - host, so `run` needs `wasmtime_wasi::p3` and wasmtime-wasi-http's p3 linker, and, for - `wasi:http/handler`, a way to receive requests, as `--invoke` can't build one. - -## Spike results - -[`tests/fixtures/spike-p3-race`](../tests/fixtures/spike-p3-race) tests steps in wasmtime 49, with -hand-written components: a guest whose `run` calls `fetch("a")` and `fetch("b")` concurrently and -joins the results in the order its wakeups receive them; a record proxy whose `fetch` forwards to -wasmtime's `wait-for`, with delays from `DELAY_A` and `DELAY_B` or random, and whose executor -counts its steps; a replay proxy that delivers each result in its recorded step; and a driver -that runs the executor alongside the guest. `make -C tests/fixtures/spike-p3-race` records each -scenario, replays it, and requires the same result, the same trace, and the same results in each -of the guest's wakeups. - -- **The clock works.** All scenarios replay exactly: each result first (`"AB"`, `"BA"`), each call - RETURNED, both RETURNED, the guest busy until both results are ready, and random delays. -- **RETURNED needs the host to be host functions.** With the host as a component instead - (`make -C tests/fixtures/spike-p3-race component-host`), a call the proxy saw RETURNED reached - the guest as STARTED, and the guest got the two results in separate wakeups, while replay gave - them together. wasmtime runs the proxy's call into a component as a queued work item, which - suspends the proxy while it is entered, and holds back the guest's next call into it - (`do_not_enter`, `concurrent.rs:774`). Calling a host component in replay as well made other - scenarios diverge, as a component host suspends differently in record and replay. Removing it, - i.e. forwarding to host functions, fixed it. -- **Sync WASI 0.2 calls change the schedule.** With the proxy and the guest printing each line - with `println!`, several scenarios diverged; buffering the output fixed them. -- **Two results never reached the guest in one wakeup**, through the proxy, nor, in one-off - experiments outside the spike, through a component awaiting `wait-for` directly, or with the guest - awaiting two `wait-for`s itself, as a wit-bindgen guest takes one event per wakeup. wasmtime gave - the lowest waitable first: `a` first, even when `b`'s timer fired first. Only both RETURNED, - without events, gave both at once. -- **wit-bindgen moves a pending call between tasks.** The proxy's export polls the host call once, - for RETURNED, and hands it to the executor otherwise; the executor's poll moves its waitable to - the executor's task (wit-bindgen's `waitable.rs`, "cross-task situations"). +Without read-ahead, a guest's copy never completes at once, which some guests may behave +differently on. With it, the host is read earlier and more than the guest asked, which a host that +produces only when read would see, and a copy line is no longer one rendezvous of the guest's. +Record mode doesn't read ahead. -## Replay +### Hosts and guests the design doesn't cover -Replay runs `composed.wasm`, with no host imports, as for p2. The imports component mocks every -import from the trace, the exports component's `start` makes the export calls, and the same -executor runs both. - -- **Host lines are performed** in their step `at`, or within the guest's call for those without one: - a `Return` or `Started` of an import, the chunk of a `Write`, the take of a `Read`, a host `Drop`, - the next export `Call`. An async mock whose call was RETURNED returns before it awaits, so the - guest sees RETURNED; a STARTED one returns after. -- **Guest lines are checked** when they happen: an import call with its arguments, an export's - result, the items of a copy from the guest, a guest drop. A mismatch is a divergence, reported - with the expected and the actual line. -- **Divergence** can also show up as a stall, when nothing can reach the next line; wasmtime then - traps with "deadlock detected", and the recorder prints the line it was waiting for. -- **Recorder interface** (`assets/recorder.wit`): `types` mirrors the Rust types; `record` - records a line; `replay` has `next-host-line`, the next line `start` performs, an export call - or the host's drop of a resource of the guest; `take-host-line`, the next host line about a task - or a handle, e.g. the `Return` of a STARTED call, which its mock waits for; `check`, which - checks a guest line, up to renaming, and returns the host lines that answer it at once, e.g. an - import's `Return`; `pair`, which pairs a label of a stream or future replay makes; `finish`, - which checks that replay reached every line; `trace-cut`, whether the trace ends during an - export call, e.g. in a trap, whose last guest copies may be missing; and, in both, `fail`, - which reports a panic of a generated component, which has no stderr of its own to print it. - Waiting for a step is the executor's, not the recorder's. The bundled recorder - (`assets/recorder*.wasm`) and the host recorder of `run` (`--use-host-recorder`) implement it, - with `Replay` of `crates/trace`. +- **Hosts that are components**, e.g. virtualized WASI composed with the guest, suspend the proxy + as it calls them, so that the guest sees STARTED where the proxy saw RETURNED; replay would have + to reproduce their suspensions. +- **Guests that drain several pending events**, or poll their waitable sets, unlike wit-bindgen's; + one answer per run would leave them nothing to drain. +- **Sync exports** can't run the executor in record mode, so host work they start, e.g. a stream + they return, makes no progress until an async export runs it. +- **`proxy-component run`** doesn't host p3 components yet: it needs wasmtime's p3 WASI and HTTP + linkers, and a way to call a handler. -## Correctness +### Which way to portability -Two questions, of which only the first can be guaranteed: - -1. **Soundness**: every run that recording sees replays exactly. -2. **Coverage**: recording sees every schedule the composition could have. It doesn't; see - [What recording can't reach](#what-recording-cant-reach). Replay only has to reproduce what was - recorded, and covering other schedules is exploration. - -### Soundness, by induction on steps - -A run is a function of its trace if the composition is deterministic apart from the inputs the -trace records: the classic condition for deterministic replay, that every nondeterministic choice -is recorded. Here it is an induction on steps: if record and replay are in the same state when -step `n` begins, and step `n` performs the same host lines, they are in the same state when step -`n + 1` begins. Between steps, the guest's lines are checked, and its calls answered by the lines -without an `at`. The induction holds under the requirements of -[Why the step is enough](#why-the-step-is-enough-and-a-gate-isnt-needed), and two more, none of -them proven yet: - -- **The executor is the only way in.** Every host event reaches the guest in a run of the - executor; see [Recording](#the-executor-delivers-and-records-host-events). -- **The trace holds every input**: the values, RETURNED or STARTED, and the step of each host - line. - -Of the requirements, deterministic runtime choices are the one the component model doesn't -guarantee (`Concurrency.md` allows nondeterminism); wasmtime 49 happens to make them, which is why -replay in another runtime needs [the profile or the shim](#portability). - -### Fork-join program - -In a fork-join program without determinacy races, every schedule gives the same result (the -result behind Feng and Leiserson's SP-bags), so for such a guest, recording the values would be -enough, without steps. A component model async guest isn't one in general: it can observe the -order in which completions arrive, as the spike's guest does, joining results in the order its -wakeups receive them, and as a `select` does. That is a nondeterministic merge, outside fork-join's -determinacy, and it is what steps record. The theory tells which guests need steps, not that steps -are enough. - -For coverage, the theory to use is systematic concurrency testing: stateless model checking with -partial-order reduction (DPOR, as in CHESS), which, for a bounded program, visits every class of -schedules that differ only in the order of independent steps. The trace's partial order fits it, -but deciding which steps are independent needs caller identity on import calls, which only [the -shim](#portability) could provide; see No guest task on import calls, in -[Trace format](#trace-format). - -### Checking the implementation - -- **Replay checks itself, for each trace.** It checks every guest line, so a wrong replay shows up - as a divergence or a stall, not silently. It can miss only what the trace doesn't record, such - as guest-internal streams, or what the guest's waits returned, which the shim could record. -- **A fixpoint test**: record a run, replay it while recording, and require the two traces to be - equal. The spike does this, for its scenarios. -- **Fuzzed schedules, replayed**: `make test-fuzz-replay` fuzzes the p2, p3 and p3-record - fixtures, `wasi_http_p3`, and the spike's guest, and replays each trace, which has to end as - the fuzzing run did: to the end, or in the guest's trap, e.g. a panic on an error the fuzzed - host returned, which is what fuzzing finds. A trap in a generated component or the recorder, - e.g. a divergence, fails it (`tests/run-wasm.sh`). -- **Enumerated scenarios**: for small guests, every placement of host completions across steps, - rather than random delays, and guests that merge nondeterministically, which are the ones steps - exist for. -- **The assumptions, checked at run time**: in replay, a step count that differs from the - recording's, which replay reports; in record mode, a host event that the guest observes outside - a step. -- **A model of the spec.** The canonical ABI's reference, `definitions.py`, is executable, and - marks its nondeterministic choices. Modeling the guest, the proxy and the scheduler there, and - checking exhaustively, for small programs, that replay at the recorded steps gives the same run, - would test the design independently of wasmtime: the closest to a proof, short of writing one. +Replay in another runtime needs its choices to match: a deterministic profile, agreed in the +component model and adopted by runtimes, or a shim, which works without anyone's agreement but +rewrites the guest. Which one, or both, is open. -## Portability +### Smaller ones -**Replay in the runtime that recorded needs no change to anything.** It needs the runtime's -choices to be deterministic given the same inputs, the same runtime version and configuration, -and the same executor in record and replay. - -- **wasmtime 49** makes its choices without randomness. -- **jco** replays a composition with no async host imports with its fixed rules; its - `asyncDeterminism` setting is for this. Recording in jco meets its RETURNED race in - `_lowerImport`, which only changes what the proxy sees from the host, and the proxy records what - it saw. - -**Replay in another runtime** needs the runtime's choices to be the same in both. Two ways: - -1. **A deterministic scheduling profile** in the component model, as core Wasm 3.0 has for NaNs - and relaxed SIMD: fix each choice `Concurrency.md` lists, e.g. the lowest waitable first, the - oldest ready thread first, and when a host completion is delivered relative to ready wasm - tasks. A profile fixes the choices rather than recording them, so the composition stays as it - is. - - The process: an issue, then a PR to `WebAssembly/component-model`, updating - `design/mvp/Concurrency.md` and the reference `design/mvp/canonical-abi/definitions.py` - with tests, discussed in the WebAssembly CG's component model meetings; runtimes adopt it - behind a feature flag, as they do async (🚝) and error-context (📝). - - wasmtime and jco already have deterministic rules, but different ones, so the profile picks - one at each point. -2. **A shim inside the guest**, without anyone's agreement: rewrite the guest component to put a - core module between its core instance and its async built-ins (`waitable-set.wait`, `poll`, - `thread.yield`, the stream and future operations) and callback exports. The built-ins are - defined inside the guest component, and act on its own tables, so this can't be a separate - component; the shim reports each observation (the event a wait returned, a copy that blocked) - through a new import, e.g. `proxy:sched/hook`, composed with the proxy. Replay then releases - exactly what each recorded wait saw. The shim observes; replacing the built-ins would mean - reimplementing the component model's async runtime inside the guest. Positions by the guest's - waits, rather than by the executor's steps, also hold when the guest is suspended where the - proxy can't see it, e.g. by an epoch deadline that yields, or a blocking WASI 0.2 call: replay - releases each host event so that every wait finds the same events pending as in the - recording. - -Patching the runtime, e.g. a scheduling hook in wasmtime, would also work, but replay must not -depend on runtime patches. wasmtime 49 has the start of its own record and replay -(`Config::rr`), without concurrency yet. - -## Phases - -1. **Spike, in wasmtime**: done, see [Spike results](#spike-results). -2. **Trace format** in `crates/trace`, and the recorder interface: done. -3. **Replay**, tested on traces that fuzz mode writes, with the checks of - [Correctness](#checking-the-implementation): done. It replays 100 of 100 fuzz traces of the - p3 fixture, and 60 of 60 of `wasi_http_p3`. -4. **Record**, with the executor and its pumps, under `wasmtime serve` and `run`. Async imports, - streams and futures are done: `make test-record-p3` records `tests/fixtures/p3-record` - under `wasmtime run`, with delays that make its calls RETURNED or STARTED, and stdin copied to - stderr through their streams, and replays each trace; `make test-record-http-p3` records - `tests/wasi_http_p3.wasm` under `wasmtime serve`, for several requests, and replays it. Next - are `proxy-component run` as a host, and the ends the guest hands back (see - [Labels](#labels)). -5. **Portability across runtimes**: the profile proposal, or the shim. - -## Open questions - -1. **Steps with several arrivals**, and the host's side of stream copies (wasmtime's - `StreamProducer::poll_produce`, which is called when the guest reads): whether counting - steps places them exactly. -2. **[Read-ahead in pumps](#read-ahead-in-pumps)**: whether to read ahead, which makes the - guest's immediate copies reachable, at the cost of the host's view, and of copy lines that - record the host's chunks rather than the guest's. Record mode doesn't, for now. -3. **`list`** as WAVE lists or byte strings. -4. **Which way to portability**: the profile, the shim, or both. -5. **Hosts that are components**, e.g. virtualized WASI composed with the main component: whether - to support recording them, which needs the guest to always see STARTED, or the replay to - reproduce the component host's suspensions. -6. **Guests that see several events at once**: bindings that drain pending events, or call - `waitable-set.poll`, unlike wit-bindgen's; the spike covers only wit-bindgen guests. -7. **The recorder's output**: buffering it to the end, or only a host recorder; and where to - write a buffer when the run traps, or doesn't end, as under `wasmtime serve`. -8. **The steps of the exports component's jobs**, e.g. reading the stream `serve` returned, - which run on the task of `start`, not in the executor, so that their lines have the last step - rather than their own. They replay in every test so far. To place them exactly, the exports - component could hand its ends of the guest's streams and futures to the imports component's - executor, with a function per payload type. +- `list` items are WAVE lists; byte strings would be shorter. +- No caller identity: a trace can't tell which guest lines a host line caused, which limits + deriving other schedules from it, e.g. for systematic exploration; the shim could add it.

)wx1EJBGbw1mtz{+x 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 Z3S~Eu$N2}wfXQWM- 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�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