|
125 | 125 | .ifndef DSWS2_CONV |
126 | 126 | .set DSWS2_CONV, 0 // 0 = pre-conversion static substrate (Phase A green); 1 = Phase B |
127 | 127 | .endif |
| 128 | +.ifndef DSWS2_TICKET_SELFTEST |
| 129 | + .set DSWS2_TICKET_SELFTEST, 0 // DIAG-only try_gate single-winner smoke (Task 4 Step 3); default 0 = no bytes |
| 130 | +.endif |
128 | 131 | .set SNAP_BASE, (INITFLAG_OFF + 4) // u32[6]: [parity*3 + {0:nC,1:nA,2:nB}] role-mix snapshots |
129 | 132 | .set QUIESCE_CNT_OFF,(SNAP_BASE + 6*4) // u32 role-agnostic bail counter |
130 | 133 | .set DSWS2_STATE_END,(QUIESCE_CNT_OFF + 4) |
|
386 | 389 | s_min_u32 s61, s60, \dst_b // cons_b = min(clock, prod_b) |
387 | 390 | s_sub_u32 \dst_b, \dst_b, s61 // occ_B = prod_b - cons_b in [0,FN] |
388 | 391 | .endm |
| 392 | + |
| 393 | +// -------------------------------------------------------------------------------------------- |
| 394 | +// Phase-B controller thresholds + sum-envelope budget (Task 4). EPOCH_SHIFT mirrors coop / |
| 395 | +// occ_dispatch (epoch = segcnt >> EPOCH_SHIFT). BUDGET is the per-WG VGPR sum-envelope ceiling |
| 396 | +// the reservation counter must never exceed; default = the launch reservation, which makes the |
| 397 | +// envelope a strict conservation law (a feed->compute grow can only fit if a compute->feed shrink |
| 398 | +// already freed the delta). Task 5 may re-tune via `-defsym BUDGET=` if per-SIMD headroom exists. |
| 399 | +// -------------------------------------------------------------------------------------------- |
| 400 | +.ifndef EPOCH_SHIFT |
| 401 | + .set EPOCH_SHIFT, 3 // decision clock: epoch = segcnt >> EPOCH_SHIFT (small = reactive) |
| 402 | +.endif |
| 403 | +.ifndef BUDGET |
| 404 | + .set BUDGET, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN) // = VRESV_OFF init (conservation ceiling) |
| 405 | +.endif |
| 406 | + |
| 407 | +// try_gate: the lock-free single-winner conversion ticket (transcribed VERBATIM from occ_kernel_coop.s, |
| 408 | +// which transcribes dsws_ctrl_model.cpp gate_try_win + epoch_of EXACTLY). E = segcnt>>EPOCH_SHIFT. |
| 409 | +// gate[dir] holds the last epoch dir fired. Among many waves racing the same (g<E), exactly ONE wins |
| 410 | +// per epoch via the LDS compare-swap; the rest see g advanced. \swin <- 1 iff THIS wave won the |
| 411 | +// (dir,epoch) ticket, else 0. Read-only on state besides gate[dir]; NO role actuation here (Task 5 |
| 412 | +// acts on \swin). Scratch: s62..s65 (free at the Task-5 occ_sample->try_gate->reserve_try point -- |
| 413 | +// occ_sample's s62/s63 result is consumed into `dir` BEFORE this runs), v5/v6/v7 (<=v15: pre-grow / |
| 414 | +// lean-safe). CAS operand order (gfx1201, GCN order -- NOT flipped, KG 9ed04f3c): |
| 415 | +// ds_cmpstore_rtn_b32 vdst,vaddr,vNEW,vCMP -> MEM=(MEM==vCMP)?vNEW:MEM, vdst<-old. So vsrc0=E (new), |
| 416 | +// vsrc1=g (compare). WIN iff returned-old == g. (Swapping them leaves gate stuck so old==g for ALL |
| 417 | +// racers -> every racer "wins" -> would-win ~= NCOMP*epochs instead of ~= epochs.) |
| 418 | +.macro try_gate dir, swin |
| 419 | + lds_get s62, SEGCNT_OFF // E = epoch_of(segcnt, EPOCH_SHIFT) |
| 420 | + s_lshr_b32 s62, s62, EPOCH_SHIFT |
| 421 | + lds_get s63, (GATE_OFF + (\dir)*4) // g = gate[dir] |
| 422 | + s_mov_b32 \swin, 0 |
| 423 | + s_cmp_ge_u32 s63, s62 // g >= E -> dir already fired this/later epoch -> lose |
| 424 | + s_cbranch_scc1 .Ltg_done\@ |
| 425 | + s_mov_b32 s65, exec_lo // lane0-only CAS (one ticket attempt per WAVE) |
| 426 | + v_cmp_eq_u32 vcc_lo, 0, v2 |
| 427 | + s_and_b32 exec_lo, exec_lo, vcc_lo |
| 428 | + s_cbranch_execz .Ltg_restore\@ |
| 429 | + v_mov_b32 v5, (GATE_OFF + (\dir)*4) // vaddr = &gate[dir] |
| 430 | + v_mov_b32 v6, s63 // v6 = g (vsrc1 = CMP/expected) |
| 431 | + v_mov_b32 v7, s62 // v7 = E (vsrc0 = NEW value to store) |
| 432 | + ds_cmpstore_rtn_b32 v6, v5, v7, v6 // gate[dir] = (gate[dir]==g) ? E : gate[dir]; v6 <- old |
| 433 | + s_wait_dscnt 0x0 |
| 434 | +.Ltg_restore\@: |
| 435 | + s_mov_b32 exec_lo, s65 |
| 436 | + v_readfirstlane_b32 s64, v6 // s64 = old (lane0's CAS result, broadcast) |
| 437 | + s_cmp_eq_u32 s64, s63 // WIN iff old == g (we were the swapper) |
| 438 | + s_cbranch_scc0 .Ltg_done\@ |
| 439 | + s_mov_b32 \swin, 1 |
| 440 | +.Ltg_done\@: |
| 441 | +.endm |
| 442 | + |
| 443 | +// reserve_try: the VGPR sum-envelope reservation (transcribes reserve_grow, dsws_ctrl_model.cpp:47). |
| 444 | +// Reserve first (atomic add of SIGNED \delta on vgpr_reserved), then validate prev+delta <= BUDGET; |
| 445 | +// on over-budget cleanly UNDO (atomic add of -\delta) and reject. The LDS atomic serializes the <=2 |
| 446 | +// concurrent grows an epoch permits: the second to validate sees the first's reservation and backs off. |
| 447 | +// GROW (feed->compute): pass \delta = +(NFV-VLEAN). Over-budget -> undo, \won=0 (stay in role). |
| 448 | +// SHRINK(compute->feed): pass \delta = -(NFV-VLEAN). new = prev+delta < prev <= BUDGET, so the |
| 449 | +// validate branch is a proven no-op -> \won=1 ALWAYS (shrink never fails). |
| 450 | +// One macro, one call site (Task 5 `reserve_try delta, s_ok`); direction is the sign of \delta. |
| 451 | +// Scratch: s62/s63 (free at the bail-commit point -- try_gate's s62..s65 are long dead by then). |
| 452 | +.macro reserve_try delta, won |
| 453 | + lds_fetch_add s62, VRESV_OFF, (\delta) // s62 = prev reserved; vgpr_reserved += delta |
| 454 | + s_add_u32 s63, s62, (\delta) // s63 = new reservation = prev + delta |
| 455 | + s_mov_b32 \won, 1 |
| 456 | + s_cmp_le_u32 s63, BUDGET // new <= BUDGET -> commit (win); shrink always passes |
| 457 | + s_cbranch_scc1 .Lrt_done\@ |
| 458 | + lds_fetch_add s62, VRESV_OFF, -(\delta) // over-budget: undo the reservation, reject |
| 459 | + s_mov_b32 \won, 0 |
| 460 | +.Lrt_done\@: |
| 461 | +.endm |
389 | 462 | .endif |
390 | 463 |
|
391 | 464 | // ============================================================================================ |
@@ -434,6 +507,24 @@ occ_kernel: |
434 | 507 | v_readfirstlane_b32 s24, v1 // wid (uniform per wave) |
435 | 508 | s_cmp_eq_u32 s24, 0 |
436 | 509 | s_cbranch_scc1 .Lclaimer |
| 510 | +.if DSWS2_CONV && DIAG && DSWS2_TICKET_SELFTEST |
| 511 | + // Task 4 Step 3 -- try_gate single-winner SMOKE (assemble-only stub; default off). Every non-claimer |
| 512 | + // wave races the (dir=0) ticket ONCE and atomic-adds its win (0/1) into occ[28] (byte offset 112, |
| 513 | + // clear of the 0/20/24/104/108 control+probe words). On GPU (Task 6, if enabled) the sum should land |
| 514 | + // near #epochs, NOT NCOMP*#epochs -- the harness-side proof the LDS-CAS yields <=1 winner/(dir,epoch). |
| 515 | + // v4=0 (set in prologue), v2=lane; try_gate temps v5/v6/v7 are <=v15 (pre-grow safe). wid (s24) |
| 516 | + // survives -- try_gate touches only s62..s65 / s16. NOTE: pre-init-rendezvous placement -> a real run |
| 517 | + // reads gate/segcnt before the claimer publishes them; fine for an assemble/smoke stub. |
| 518 | + try_gate 0, s50 |
| 519 | + v_cmp_eq_u32 vcc_lo, 0, v2 |
| 520 | + s_mov_b32 s16, exec_lo |
| 521 | + s_and_b32 exec_lo, exec_lo, vcc_lo |
| 522 | + s_cbranch_execz .Ltg_selftest_skip |
| 523 | + v_mov_b32 v5, s50 // win flag (0/1) for THIS wave |
| 524 | + global_atomic_add_u32 v4, v5, s[0:1] offset:112 scope:SCOPE_DEV // occ[28] += win |
| 525 | +.Ltg_selftest_skip: |
| 526 | + s_mov_b32 exec_lo, s16 |
| 527 | +.endif |
437 | 528 | s_cmp_lt_u32 s24, NBFEED |
438 | 529 | s_cbranch_scc1 .Lbfeed |
439 | 530 | s_cmp_lt_u32 s24, (NBFEED+NAFEED) |
|
0 commit comments