grafos_std/cpu_shared/
mod.rs

1//! Shared-memory tasklet SDK surface (Phase 48.3 P3 + P3.1).
2//!
3//! This module exposes the *programmer-facing* types for the shared-memory
4//! tasklet execution mode: a coordinator context, a worker context, typed
5//! views over the shared region and per-worker scratch, and partition
6//! helpers.
7//!
8//! ## Dual implementation — one vocabulary
9//!
10//! There are two concrete implementations of the public types
11//! (`CoordinatorCtx`, `WorkerCtx`, `SharedRegion`, `WorkerScratch`,
12//! `SharedTaskletBuilder`):
13//!
14//! - [`host_mock`] — used on native host targets when the `std` feature is
15//!   enabled. Runs coordinator + worker closures directly on host threads
16//!   via `std::thread::scope` and `std::sync::Barrier`. This is the
17//!   testing / prototyping path — it is NOT the runtime.
18//! - [`guest`] — used when compiling for `wasm32-unknown-unknown`. Wraps
19//!   the `grafos_worker_v0` WASM imports in safe types. A program compiled
20//!   through this path runs inside the real shared-memory tasklet runtime
21//!   (P1 track) on a grafOS host.
22//!
23//! A cfg-gated `pub use` below picks the right implementation for the
24//! current target, so programmers writing a tasklet import
25//! `grafos_std::cpu_shared::{CoordinatorCtx, WorkerCtx, ...}` without
26//! worrying about which target they're building for. The public names are
27//! identical across both implementations.
28//!
29//! ## Lifecycle difference (important)
30//!
31//! The lifecycle of a shared-memory tasklet is not symmetric across
32//! targets, and this is reflected in how execution is kicked off:
33//!
34//! - **Host**: the SDK initiates execution. Call
35//!   [`FabricCpu::shared_memory_tasklet`](crate::cpu::FabricCpu::shared_memory_tasklet)
36//!   to obtain a [`SharedTaskletBuilder`], configure it, then call
37//!   `.launch(coord_fn, worker_fn)`. The builder drives the closures on
38//!   host threads.
39//! - **wasm32 guest**: the *runtime* initiates execution. The guest
40//!   module exports a `tasklet_run(in_ptr, in_len, out_ptr, out_cap) -> i32`
41//!   function that the runtime calls on every worker. Inside that export
42//!   the guest calls [`run_shared_memory_tasklet`] with the same
43//!   coordinator and worker closures. `run_shared_memory_tasklet`
44//!   dispatches to the coordinator closure on worker 0 and the worker
45//!   closure on workers 1..N, constructs the appropriate context from the
46//!   linear-memory offsets published by `grafos_worker_v0`, and returns
47//!   the i32 status.
48//!
49//! The *inner* closures look identical on both targets — same
50//! [`CoordinatorCtx`] / [`WorkerCtx`] API, same [`partition`] helpers,
51//! same typed shared/scratch access. Only the entry-point shape differs.
52//!
53//! ## Source portability (Phase 48.14)
54//!
55//! Source-level portability between host mock and wasm32 guest is a
56//! documented, narrower subset of the promised behavioral portability.
57//! See the "Source portability" section of
58//! `docs/grafos/shared-memory-tasklet-programming-model.md` for the
59//! authoritative safe-list / cfg-list / behavior-divergent
60//! classification, and "Cross-target source patterns" in
61//! `docs/grafos/shared-memory-tasklet-build-guide.md` for the canonical
62//! `#[cfg]`-gated skeleton. Methods tagged with `**Target-specific.**`
63//! in their rustdoc are on the cfg-list; methods tagged with
64//! `**Behavior diverges across targets.**` compile on both targets but
65//! mean different things.
66//!
67//! ## Programming model (host mock example)
68//!
69//! ```no_run
70//! # #[cfg(all(feature = "std", not(target_arch = "wasm32")))] {
71//! use grafos_std::cpu::{CpuBuilder, CoordinatorCtx, WorkerCtx};
72//! use std::sync::atomic::{AtomicU32, Ordering};
73//!
74//! #[derive(Default)]
75//! struct Shared {
76//!     counter: AtomicU32,
77//! }
78//! #[derive(Default, Clone)]
79//! struct Scratch {
80//!     local: u32,
81//! }
82//!
83//! grafos_std::host::reset_mock();
84//! let lease = CpuBuilder::new().cores(4).acquire().unwrap();
85//! let _ = lease.cpu().shared_memory_tasklet::<Shared, Scratch>()
86//!     .cores(4)
87//!     .workers(4)
88//!     .shared_bytes(1024)
89//!     .scratch_bytes_per_worker(64)
90//!     .fuel(1_000_000)
91//!     .launch(|coord: &mut CoordinatorCtx<Shared, Scratch>| {
92//!         coord.parallel_for_workers(|w| {
93//!             w.scratch_mut().local = w.worker_index() as u32;
94//!             w.shared().counter.fetch_add(1, Ordering::SeqCst);
95//!         }).unwrap();
96//!         let n = coord.shared().counter.load(Ordering::SeqCst);
97//!         coord.set_output(&n.to_le_bytes());
98//!         Ok(())
99//!     });
100//! # }
101//! ```
102
103// Partition helpers are pure integer math — they are available on every
104// target, regardless of target_arch or the `std` feature.
105pub mod partition;
106
107#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
108pub mod host_mock;
109
110#[cfg(target_arch = "wasm32")]
111pub mod guest;
112
113#[cfg(target_arch = "wasm32")]
114pub mod trap_allocator;
115
116#[cfg(target_arch = "wasm32")]
117pub use trap_allocator::TrapAllocator;
118
119// ---------------------------------------------------------------------------
120// Cfg-gated re-exports. A programmer who writes
121// `use grafos_std::cpu_shared::{CoordinatorCtx, WorkerCtx, ...}`
122// gets the right implementation for the current target without thinking
123// about it.
124// ---------------------------------------------------------------------------
125
126#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
127pub use host_mock::{
128    Cancelled, CoordinatorCtx, FuelExhausted, SharedRegion, SharedTaskletBuilder,
129    SharedTaskletResult, TaskletError, WorkerCtx, WorkerScratch,
130};
131
132#[cfg(target_arch = "wasm32")]
133pub use guest::{
134    run_shared_memory_tasklet, Cancelled, CoordinatorCtx, FuelExhausted, SharedRegion, SharedState,
135    SharedTaskletResult, TaskletError, WorkerCtx, WorkerScratch,
136};
137
138// ---------------------------------------------------------------------------
139// Re-export resolution smoke test (host target).
140//
141// This guards against accidentally breaking the cfg-gated re-export: it
142// imports each public name through the module root and verifies the
143// types resolve. If someone refactors the re-export incorrectly the test
144// will fail to compile, which is exactly the signal we want.
145// ---------------------------------------------------------------------------
146#[cfg(all(test, feature = "std", not(target_arch = "wasm32")))]
147mod reexport_smoke {
148    #[test]
149    fn reexports_resolve_to_host_mock() {
150        use crate::cpu_shared::{
151            partition, CoordinatorCtx, SharedRegion, SharedTaskletBuilder, SharedTaskletResult,
152            TaskletError, WorkerCtx, WorkerScratch,
153        };
154        // Touch each type-level name so the compiler proves it resolves.
155        let _ = core::mem::size_of::<CoordinatorCtx<'static, u32, u32>>();
156        let _ = core::mem::size_of::<WorkerCtx<'static, u32, u32>>();
157        let _ = core::mem::size_of::<SharedRegion<u32>>();
158        let _ = core::mem::size_of::<WorkerScratch<u32>>();
159        let _ = core::mem::size_of::<SharedTaskletBuilder<'static, u32, u32>>();
160        let _ = core::mem::size_of::<SharedTaskletResult>();
161        let _ = core::mem::size_of::<TaskletError>();
162        // Partition helpers are target-independent.
163        let r = partition::range(10, 0, 2);
164        assert_eq!(r, 0..5);
165    }
166}