Repository navigation
Expand file tree
/
Copy pathgrid_work.rs
More file actions
347 lines (319 loc) · 13.1 KB
/
Copy pathgrid_work.rs
File metadata and controls
347 lines (319 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
//! Cross-block work coordination for GridScope.
//!
//! Provides work-dispatch primitives that let a coordinator block (block 0)
//! distribute work to independently-scheduled worker blocks via global memory.
//! Designed for SM75 WITHOUT cooperative launch — blocks are independently
//! scheduled and may not all be resident simultaneously.
//!
//! # Architecture
//!
//! ```text
//! Coordinator (block 0) Worker blocks (1..N)
//! ┌─────────────────────┐ ┌─────────────────────┐
//! │ alloc_work_slots(N) │ │ grid_worker_loop() │
//! │ │ │ poll slot.status │
//! │ dispatch_work(slot, │──[global]──│ if WORK_AVAILABLE │
//! │ fn_ptr, args) │ memory │ execute work │
//! │ │ │ write result │
//! │ poll results │◄─[global]──│ set COMPLETED │
//! └─────────────────────┘ memory └─────────────────────┘
//! ```
//!
//! # Status state machine
//!
//! ```text
//! IDLE ──[coordinator writes work]──► WORK_AVAILABLE
//! ▲ │
//! │ [worker picks up]
//! │ ▼
//! └────[coordinator reads result]── COMPLETED
//!
//! Any state ──[coordinator cancels]──► CANCELLED
//! ```
//!
//! # Memory model
//!
//! All status transitions use system-scope atomics (`sys_store_release_u32` /
//! `sys_spin_load_acquire_u32`) for cross-block visibility on SM75+. The
//! coordinator writes work descriptors before releasing `WORK_AVAILABLE`;
//! the worker acquires the status and is guaranteed to see the written data.
//!
//! # Example
//!
//! ```rust,ignore
//! use gpu_runtime::scope::grid_scope;
//! use gpu_runtime::grid_work::{self, BlockWorkSlot};
//!
//! // Coordinator block (block 0):
//! unsafe {
//! grid_scope(pool, pool_size, |gscope| {
//! let slots = gscope.alloc::<BlockWorkSlot>(num_workers);
//! grid_work::init_work_slots(slots);
//!
//! // Dispatch work to block 1
//! grid_work::dispatch_work(
//! &mut slots[0],
//! my_work_fn as u64,
//! [input_ptr as u64, count as u64, 0, 0],
//! );
//!
//! // Wait for completion
//! grid_work::wait_slot_completed(&slots[0]);
//! let result = grid_work::read_result(&slots[0]);
//! });
//! }
//!
//! // Worker block (block N):
//! unsafe {
//! // slot_ptr provided by host or discovered via device global
//! grid_work::grid_worker_loop(slot_ptr);
//! }
//! ```
/// Work slot status: idle, no work assigned.
pub const SLOT_IDLE: u32 = 0;
/// Work slot status: coordinator has written work, worker should execute.
pub const SLOT_WORK_AVAILABLE: u32 = 1;
/// Work slot status: worker has finished, result is available.
pub const SLOT_COMPLETED: u32 = 2;
/// Work slot status: work is cancelled, worker should exit.
pub const SLOT_CANCELLED: u32 = 3;
/// A work descriptor slot in global memory for one block.
///
/// The coordinator block writes `work_fn`, `args`, then transitions
/// `status` to [`SLOT_WORK_AVAILABLE`]. The worker block polls `status`,
/// executes the work, writes `result`, then transitions to [`SLOT_COMPLETED`].
///
/// All fields are `u64`-aligned for system-scope atomic access.
/// The `status` field is the synchronization point — it must be accessed
/// via system-scope atomics only.
///
/// # Layout
///
/// ```text
/// offset 0: status (u32, atomic — state machine)
/// offset 4: _pad (u32, alignment padding)
/// offset 8: work_fn (u64, function pointer cast)
/// offset 16: args ([u64; 4], work arguments)
/// offset 48: result (u64, written by worker)
/// ```
#[repr(C)]
#[derive(Clone, Copy)]
pub struct BlockWorkSlot {
/// Status field — accessed via system-scope atomics.
/// Values: [`SLOT_IDLE`], [`SLOT_WORK_AVAILABLE`], [`SLOT_COMPLETED`], [`SLOT_CANCELLED`].
pub status: u32,
/// Padding for u64 alignment of subsequent fields.
pub _pad: u32,
/// Function pointer for the work (as u64).
///
/// The function signature is `fn(args: &[u64; 4]) -> u64`.
/// Cast with `my_fn as fn(&[u64; 4]) -> u64 as usize as u64`.
pub work_fn: u64,
/// Work arguments (as raw u64 values). Interpretation is up to the
/// work function — typically pointers and sizes.
pub args: [u64; 4],
/// Result value (written by worker, read by coordinator after COMPLETED).
pub result: u64,
}
/// Initialize an array of work slots to [`SLOT_IDLE`].
///
/// Zeroes all fields and sets each slot's status to `SLOT_IDLE` using
/// a system-scope release store for cross-block visibility.
///
/// # Safety
///
/// - `slots` must point to valid global memory with at least `count` elements.
/// - Must be called before any worker begins polling.
pub unsafe fn init_work_slots(slots: &mut [BlockWorkSlot]) {
for slot in slots.iter_mut() {
slot._pad = 0;
slot.work_fn = 0;
slot.args = [0; 4];
slot.result = 0;
// Status must be set last with release semantics so workers see zeroed fields.
let status_ptr = &mut slot.status as *mut u32;
gpu_atomics::sys_store_release_u32(status_ptr, SLOT_IDLE);
}
}
/// Dispatch work to a specific block's work slot.
///
/// Writes the function pointer and arguments, then transitions the slot
/// status to [`SLOT_WORK_AVAILABLE`] with a system-scope release store.
/// The release guarantees that the worker block will see `work_fn` and
/// `args` after it acquires the `WORK_AVAILABLE` status.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
/// - The slot must be in [`SLOT_IDLE`] or [`SLOT_COMPLETED`] state.
/// - `work_fn` must be a valid function pointer cast to u64 with signature
/// `fn(args: &[u64; 4]) -> u64`.
/// - The coordinator must not write to the slot again until the worker
/// transitions it to [`SLOT_COMPLETED`].
pub unsafe fn dispatch_work(slot: &mut BlockWorkSlot, work_fn: u64, args: [u64; 4]) {
// Write work descriptor fields (plain stores — no ordering needed yet).
slot.work_fn = work_fn;
slot.args = args;
slot.result = 0;
// Release-store the status — makes work_fn and args visible to the worker.
let status_ptr = &mut slot.status as *mut u32;
gpu_atomics::sys_store_release_u32(status_ptr, SLOT_WORK_AVAILABLE);
}
/// Cancel a work slot.
///
/// Transitions the slot to [`SLOT_CANCELLED`] via system-scope release store.
/// If a worker is polling, it will see the cancellation and exit. If the
/// worker has already started executing, the cancellation takes effect after
/// the current work item completes (cooperative cancellation).
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
pub unsafe fn cancel_slot(slot: &mut BlockWorkSlot) {
let status_ptr = &mut slot.status as *mut u32;
gpu_atomics::sys_store_release_u32(status_ptr, SLOT_CANCELLED);
}
/// Read the status of a work slot using system-scope acquire load.
///
/// Returns the current status value. Use this for non-spinning one-shot
/// checks. For polling loops, use [`poll_slot_status`] instead.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
pub unsafe fn load_slot_status(slot: &BlockWorkSlot) -> u32 {
let status_ptr = &slot.status as *const u32;
gpu_atomics::sys_load_acquire_u32(status_ptr)
}
/// Poll the status of a work slot using spin-loop-safe system-scope acquire load.
///
/// Returns the current status value. Includes `nanosleep` to yield the
/// warp slot during spinning. Use inside polling loops.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
pub unsafe fn poll_slot_status(slot: &BlockWorkSlot) -> u32 {
let status_ptr = &slot.status as *const u32;
gpu_atomics::sys_spin_load_acquire_u32(status_ptr)
}
/// Spin-wait until a work slot reaches [`SLOT_COMPLETED`] or [`SLOT_CANCELLED`].
///
/// Returns the terminal status. Uses spin-loop-safe system-scope loads
/// with nanosleep yield.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
/// - The slot must have been dispatched (status = [`SLOT_WORK_AVAILABLE`])
/// or this will spin forever.
pub unsafe fn wait_slot_done(slot: &BlockWorkSlot) -> u32 {
loop {
let s = poll_slot_status(slot);
if s == SLOT_COMPLETED || s == SLOT_CANCELLED {
return s;
}
}
}
/// Read the result from a completed work slot.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
/// - The slot must be in [`SLOT_COMPLETED`] state (call after [`wait_slot_done`]).
/// - The result value was written by the worker with a release store before
/// transitioning to COMPLETED, so the acquire on status guarantees visibility.
pub unsafe fn read_result(slot: &BlockWorkSlot) -> u64 {
slot.result
}
/// Reset a completed or cancelled slot back to [`SLOT_IDLE`] for reuse.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
/// - The slot must be in [`SLOT_COMPLETED`] or [`SLOT_CANCELLED`] state.
/// - No worker should be polling this slot when it is reset.
pub unsafe fn reset_slot(slot: &mut BlockWorkSlot) {
slot.work_fn = 0;
slot.args = [0; 4];
slot.result = 0;
let status_ptr = &mut slot.status as *mut u32;
gpu_atomics::sys_store_release_u32(status_ptr, SLOT_IDLE);
}
/// Worker block main loop. Polls the work slot assigned to this block.
///
/// Executes work when status transitions to [`SLOT_WORK_AVAILABLE`].
/// The work function is called with a reference to the `args` array
/// and its return value is written to `result`. After execution, the
/// slot transitions to [`SLOT_COMPLETED`].
///
/// The loop exits when:
/// - The slot transitions to [`SLOT_CANCELLED`] (coordinator requested exit)
/// - A work item completes (single-shot mode). The worker writes COMPLETED
/// and returns, allowing the coordinator to dispatch more work via
/// another call to `grid_worker_loop` or `grid_worker_loop_continuous`.
///
/// # Safety
///
/// - `slot` must point to a valid [`BlockWorkSlot`] in global memory.
/// - The function pointer stored in `work_fn` must be valid and have
/// signature `fn(args: &[u64; 4]) -> u64`.
/// - Must be called from the worker block's warp 0.
pub unsafe fn grid_worker_loop(slot: *mut BlockWorkSlot) {
loop {
let status_ptr = &(*slot).status as *const u32 as *mut u32;
let s = gpu_atomics::sys_spin_load_acquire_u32(status_ptr as *const u32);
if s == SLOT_CANCELLED {
// Coordinator requested exit — stop polling.
return;
}
if s == SLOT_WORK_AVAILABLE {
// Read work descriptor (acquire on status guarantees visibility).
let work_fn_raw = (*slot).work_fn;
let args = &(*slot).args;
// Cast and call the work function.
let work_fn: fn(&[u64; 4]) -> u64 = core::mem::transmute(work_fn_raw as usize);
let result = work_fn(args);
// Write result, then release-store COMPLETED.
(*slot).result = result;
gpu_atomics::sys_store_release_u32(status_ptr, SLOT_COMPLETED);
// Single-shot: return after one work item.
// The coordinator can dispatch more work and the worker can
// call grid_worker_loop again, or use grid_worker_loop_continuous.
return;
}
// SLOT_IDLE or SLOT_COMPLETED — keep polling (nanosleep is in
// sys_spin_load_acquire_u32).
}
}
/// Continuous worker block loop. Like [`grid_worker_loop`] but keeps
/// polling for new work after each completion instead of returning.
///
/// The loop exits only when the slot transitions to [`SLOT_CANCELLED`].
///
/// After completing a work item, the worker sets the slot to [`SLOT_COMPLETED`].
/// The coordinator must reset the slot to [`SLOT_IDLE`] (or dispatch new work
/// directly) before the worker will pick up the next item.
///
/// # Safety
///
/// Same requirements as [`grid_worker_loop`].
pub unsafe fn grid_worker_loop_continuous(slot: *mut BlockWorkSlot) {
loop {
let status_ptr = &(*slot).status as *const u32 as *mut u32;
let s = gpu_atomics::sys_spin_load_acquire_u32(status_ptr as *const u32);
if s == SLOT_CANCELLED {
return;
}
if s == SLOT_WORK_AVAILABLE {
let work_fn_raw = (*slot).work_fn;
let args = &(*slot).args;
let work_fn: fn(&[u64; 4]) -> u64 = core::mem::transmute(work_fn_raw as usize);
let result = work_fn(args);
(*slot).result = result;
gpu_atomics::sys_store_release_u32(status_ptr, SLOT_COMPLETED);
// Continue polling — coordinator will dispatch more work or cancel.
}
// SLOT_IDLE or SLOT_COMPLETED — keep polling.
}
}