|
128 | 128 | .ifndef DSWS2_TICKET_SELFTEST |
129 | 129 | .set DSWS2_TICKET_SELFTEST, 0 // DIAG-only try_gate single-winner smoke (Task 4 Step 3); default 0 = no bytes |
130 | 130 | .endif |
| 131 | +.ifndef CONV_COOLDOWN |
| 132 | + .set CONV_COOLDOWN, 0 // Task 4: per-wave post-conversion cooldown epochs. 0 = spec-faithful (no |
| 133 | + // cooldown, byte-identical to pre-Task-4); >0 damps thrash (skip N epochs |
| 134 | + // of watermark decision after a wave converts role). |
| 135 | +.endif |
| 136 | +// Task 5: deterministic bring-up hook. DSWS2_FORCE=1 makes exactly wave DSWS2_FORCE_WID convert |
| 137 | +// direction DSWS2_FORCE_DIR at epoch DSWS2_FORCE_EPOCH, watermarks bypassed -- a reproducible, |
| 138 | +// single-wave/single-epoch GPU proof of role conversion. Default DSWS2_FORCE=0 emits ZERO bytes |
| 139 | +// (byte-identical to pre-Task-5). |
| 140 | +.ifndef DSWS2_FORCE |
| 141 | + .set DSWS2_FORCE, 0 |
| 142 | +.endif |
| 143 | +.ifndef DSWS2_FORCE_WID |
| 144 | + .set DSWS2_FORCE_WID, 0 |
| 145 | +.endif |
| 146 | +.ifndef DSWS2_FORCE_DIR |
| 147 | + .set DSWS2_FORCE_DIR, 0 // 0/1 = compute->A/B ; 2/3 = A/B->compute |
| 148 | +.endif |
| 149 | +.ifndef DSWS2_FORCE_EPOCH |
| 150 | + .set DSWS2_FORCE_EPOCH, 1 |
| 151 | +.endif |
131 | 152 | .set SNAP_BASE, (INITFLAG_OFF + 4) // u32[6]: [parity*3 + {0:nC,1:nA,2:nB}] role-mix snapshots |
132 | 153 | .set QUIESCE_CNT_OFF,(SNAP_BASE + 6*4) // u32 role-agnostic bail counter |
133 | 154 | .set DSWS2_STATE_END,(QUIESCE_CNT_OFF + 4) |
|
404 | 425 | .set BUDGET, (NCOMP*NFV + (NAFEED+NBFEED)*VLEAN) // = VRESV_OFF init (conservation ceiling) |
405 | 426 | .endif |
406 | 427 |
|
| 428 | +.if DSWS2_CONV |
| 429 | +// compile-time no-parking invariant: every launched wave must fit lean at once |
| 430 | +.if (WAVES * VLEAN) > BUDGET |
| 431 | + .error "WAVES*VLEAN exceeds BUDGET — pool cannot stay all-lean (parking is out of scope)" |
| 432 | +.endif |
| 433 | +.endif |
| 434 | + |
407 | 435 | // try_gate: the lock-free single-winner conversion ticket (transcribed VERBATIM from occ_kernel_coop.s, |
408 | 436 | // which transcribes dsws_ctrl_model.cpp gate_try_win + epoch_of EXACTLY). E = segcnt>>EPOCH_SHIFT. |
409 | 437 | // gate[dir] holds the last epoch dir fired. Among many waves racing the same (g<E), exactly ONE wins |
|
550 | 578 | .Lca_commit\@: |
551 | 579 | lds_fetch_add s52, \dst_slot, 1 // (c) inc dest slot (unbounded -> plain atomic add) |
552 | 580 | s_mov_b32 s59, \dst_slot // flip private current-role reg (records new role slot id) |
| 581 | +.if CONV_COOLDOWN > 0 |
| 582 | + s_mov_b32 s66, CONV_COOLDOWN // Task 4: committed conversion -> arm cooldown |
| 583 | +.endif |
553 | 584 | // ---- s_alloc_vgpr resize: THE pre-grow OOR window closes here; all reads above were <=v15 ---- |
554 | 585 | .Lca_alloc\@: |
555 | 586 | s_alloc_vgpr \alloc_sz // GROW(NFV) / SHRINK(32); SCC-retry (brick-class rule) |
@@ -622,11 +653,29 @@ occ_kernel: |
622 | 653 | .Ltg_selftest_skip: |
623 | 654 | s_mov_b32 exec_lo, s16 |
624 | 655 | .endif |
| 656 | +.if DSWS2_CONV |
| 657 | +.if CONV_COOLDOWN > 0 |
| 658 | + s_mov_b32 s66, 0 // Task 4: init cooldown ctr (un-cooled at entry) |
| 659 | +.endif |
| 660 | + s_cmp_lt_u32 s24, NBFEED |
| 661 | + s_cbranch_scc1 .Lseed_bfeed |
| 662 | + s_cmp_lt_u32 s24, (NBFEED+NAFEED) |
| 663 | + s_cbranch_scc1 .Lseed_afeed |
| 664 | + s_mov_b32 s59, NCOMP_SLOT |
| 665 | + s_branch .Ldispatch |
| 666 | +.Lseed_afeed: |
| 667 | + s_mov_b32 s59, NAFEED_SLOT |
| 668 | + s_branch .Ldispatch |
| 669 | +.Lseed_bfeed: |
| 670 | + s_mov_b32 s59, NBFEED_SLOT |
| 671 | + s_branch .Ldispatch |
| 672 | +.else |
625 | 673 | s_cmp_lt_u32 s24, NBFEED |
626 | 674 | s_cbranch_scc1 .Lbfeed |
627 | 675 | s_cmp_lt_u32 s24, (NBFEED+NAFEED) |
628 | 676 | s_cbranch_scc1 .Lafeed |
629 | 677 | s_branch .Lcompute |
| 678 | +.endif |
630 | 679 |
|
631 | 680 | // ============================================================================================ |
632 | 681 | // A3 -- .Lclaimer : pinned wid-0. Owns the super-tile claim+broadcast, the SEGCNT clock, the |
@@ -894,17 +943,46 @@ occ_kernel: |
894 | 943 | // Lean-32 -> PRE-GROW window (the feed->compute GROW closes it inside conv_apply, post all <=v15 |
895 | 944 | // reads). If the B-ring is OVER-SERVED (occ_B>CTRL_HIGH_B) grow one B-feed->compute (dir 3): |
896 | 945 | // reserve delta +(NFV-VLEAN) (may abort over BUDGET -> stay B-feed). |
| 946 | +.if CONV_COOLDOWN > 0 |
| 947 | + s_cmp_eq_u32 s66, 0 |
| 948 | + s_cbranch_scc0 .Lbfeed_cooldn // Task 4: s66!=0 -> in cooldown, skip decision |
| 949 | +.endif |
| 950 | +.if DSWS2_FORCE |
| 951 | +.if DSWS2_FORCE_DIR == 3 |
| 952 | + // Task 5: deterministic forced conversion (dir 3: B-feed -> compute), watermark bypassed. |
| 953 | + // Convert iff wid(s24)==DSWS2_FORCE_WID AND current epoch(s35)==DSWS2_FORCE_EPOCH. |
| 954 | + s_mov_b32 s58, 0 |
| 955 | + s_cmp_eq_u32 s24, DSWS2_FORCE_WID |
| 956 | + s_cbranch_scc0 .Lbfeed_cooldn |
| 957 | + s_cmp_eq_u32 s35, DSWS2_FORCE_EPOCH |
| 958 | + s_cbranch_scc0 .Lbfeed_cooldn |
| 959 | + s_mov_b32 s57, 3 // dir 3: B-feed -> compute (forced) |
| 960 | + try_gate 3, s58 |
| 961 | + conv_apply NBFEED_SLOT, NCOMP_SLOT, +(NFV-VLEAN), NFV |
| 962 | +.endif |
| 963 | +.else |
897 | 964 | s_mov_b32 s58, 0 |
898 | 965 | occ_sample s55, s56 // s55=occ_A, s56=occ_B |
899 | 966 | s_cmp_gt_u32 s56, CTRL_HIGH_B |
900 | | - s_cbranch_scc0 .Lbfeed_quiesce |
| 967 | + s_cbranch_scc0 .Lbfeed_cooldn |
901 | 968 | s_mov_b32 s57, 3 // dir 3: B-feed -> compute |
902 | 969 | try_gate 3, s58 |
903 | 970 | conv_apply NBFEED_SLOT, NCOMP_SLOT, +(NFV-VLEAN), NFV |
| 971 | +.endif |
| 972 | +.Lbfeed_cooldn: |
| 973 | +.if CONV_COOLDOWN > 0 |
| 974 | + s_cmp_eq_u32 s66, 0 // Task 4: decrement once per epoch (saturating) |
| 975 | + s_cbranch_scc1 .Lbfeed_quiesce |
| 976 | + s_sub_i32 s66, s66, 1 |
| 977 | +.endif |
904 | 978 | .Lbfeed_quiesce: |
905 | 979 | lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // commit-before-bump ordering (SPEC 3.4 decision 2) |
906 | 980 | .endif |
| 981 | +.if DSWS2_CONV |
| 982 | + s_branch .Ldispatch |
| 983 | +.else |
907 | 984 | s_branch .Lbfeed_follow |
| 985 | +.endif |
908 | 986 |
|
909 | 987 | // ============================================================================================ |
910 | 988 | // A5 -- .Lafeed : A-feed wave. Follows EPOCH/STI, decodes (mblk,ksi), stages its claimed A rowblks. |
@@ -938,17 +1016,46 @@ occ_kernel: |
938 | 1016 | // (feed->compute, s_alloc_vgpr NFV) is the ONLY grow and closes the window inside conv_apply, after |
939 | 1017 | // all <=v15 LDS/atomic reads. If the A-ring is OVER-SERVED (occ_A>CTRL_HIGH_A) grow one A-feed-> |
940 | 1018 | // compute (dir 2): reserve delta +(NFV-VLEAN) (may abort over BUDGET -> stay A-feed). |
| 1019 | +.if CONV_COOLDOWN > 0 |
| 1020 | + s_cmp_eq_u32 s66, 0 |
| 1021 | + s_cbranch_scc0 .Lafeed_cooldn // Task 4: s66!=0 -> in cooldown, skip decision |
| 1022 | +.endif |
| 1023 | +.if DSWS2_FORCE |
| 1024 | +.if DSWS2_FORCE_DIR == 2 |
| 1025 | + // Task 5: deterministic forced conversion (dir 2: A-feed -> compute), watermark bypassed. |
| 1026 | + // Convert iff wid(s24)==DSWS2_FORCE_WID AND current epoch(s35)==DSWS2_FORCE_EPOCH. |
| 1027 | + s_mov_b32 s58, 0 |
| 1028 | + s_cmp_eq_u32 s24, DSWS2_FORCE_WID |
| 1029 | + s_cbranch_scc0 .Lafeed_cooldn |
| 1030 | + s_cmp_eq_u32 s35, DSWS2_FORCE_EPOCH |
| 1031 | + s_cbranch_scc0 .Lafeed_cooldn |
| 1032 | + s_mov_b32 s57, 2 // dir 2: A-feed -> compute (forced) |
| 1033 | + try_gate 2, s58 |
| 1034 | + conv_apply NAFEED_SLOT, NCOMP_SLOT, +(NFV-VLEAN), NFV |
| 1035 | +.endif |
| 1036 | +.else |
941 | 1037 | s_mov_b32 s58, 0 |
942 | 1038 | occ_sample s55, s56 // s55=occ_A, s56=occ_B |
943 | 1039 | s_cmp_gt_u32 s55, CTRL_HIGH_A |
944 | | - s_cbranch_scc0 .Lafeed_quiesce |
| 1040 | + s_cbranch_scc0 .Lafeed_cooldn |
945 | 1041 | s_mov_b32 s57, 2 // dir 2: A-feed -> compute |
946 | 1042 | try_gate 2, s58 |
947 | 1043 | conv_apply NAFEED_SLOT, NCOMP_SLOT, +(NFV-VLEAN), NFV |
| 1044 | +.endif |
| 1045 | +.Lafeed_cooldn: |
| 1046 | +.if CONV_COOLDOWN > 0 |
| 1047 | + s_cmp_eq_u32 s66, 0 // Task 4: decrement once per epoch (saturating) |
| 1048 | + s_cbranch_scc1 .Lafeed_quiesce |
| 1049 | + s_sub_i32 s66, s66, 1 |
| 1050 | +.endif |
948 | 1051 | .Lafeed_quiesce: |
949 | 1052 | lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // commit-before-bump ordering (SPEC 3.4 decision 2) |
950 | 1053 | .endif |
| 1054 | +.if DSWS2_CONV |
| 1055 | + s_branch .Ldispatch |
| 1056 | +.else |
951 | 1057 | s_branch .Lafeed_follow |
| 1058 | +.endif |
952 | 1059 |
|
953 | 1060 | // ============================================================================================ |
954 | 1061 | // A6 -- .Lcompute : compute wave. Follows EPOCH/STI, decodes (mblk,tcol,ksi), waits resident A/B |
@@ -1075,26 +1182,81 @@ occ_kernel: |
1075 | 1182 | // else if starved for B, compute->B-feed (dir 1). Both are SHRINKs: reserve delta -(NFV-VLEAN), |
1076 | 1183 | // s_alloc_vgpr 32 (already lean -> no-op). Persistent: s57=dir, s58=s_win, s59=new-role (all outside |
1077 | 1184 | // s60..s65 so occ_sample/try_gate/reserve_try/conv_* cannot clobber them while live). |
| 1185 | +.if CONV_COOLDOWN > 0 |
| 1186 | + s_cmp_eq_u32 s66, 0 |
| 1187 | + s_cbranch_scc0 .Lcmp_cooldn // Task 4: s66!=0 -> in cooldown, skip decision |
| 1188 | +.endif |
| 1189 | +.if DSWS2_FORCE |
| 1190 | +.if DSWS2_FORCE_DIR == 0 || DSWS2_FORCE_DIR == 1 |
| 1191 | + // Task 5: deterministic forced conversion (compute is the source role for dir 0 and dir 1). |
| 1192 | + // Watermark bypassed: convert iff wid(s24)==DSWS2_FORCE_WID AND current epoch(s35)== |
| 1193 | + // DSWS2_FORCE_EPOCH. try_gate still runs (sets s58=s_win, conv_apply's precondition). |
| 1194 | + s_mov_b32 s58, 0 // s_win = 0 default (compares below may bail early) |
| 1195 | + s_cmp_eq_u32 s24, DSWS2_FORCE_WID |
| 1196 | + s_cbranch_scc0 .Lcmp_cooldn |
| 1197 | + s_cmp_eq_u32 s35, DSWS2_FORCE_EPOCH |
| 1198 | + s_cbranch_scc0 .Lcmp_cooldn |
| 1199 | +.if DSWS2_FORCE_DIR == 0 |
| 1200 | + s_mov_b32 s57, 0 // dir 0: compute -> A-feed (forced) |
| 1201 | + try_gate 0, s58 |
| 1202 | + conv_apply NCOMP_SLOT, NAFEED_SLOT, -(NFV-VLEAN), 32 |
| 1203 | +.else |
| 1204 | + s_mov_b32 s57, 1 // dir 1: compute -> B-feed (forced) |
| 1205 | + try_gate 1, s58 |
| 1206 | + conv_apply NCOMP_SLOT, NBFEED_SLOT, -(NFV-VLEAN), 32 |
| 1207 | +.endif |
| 1208 | +.endif |
| 1209 | +.else |
1078 | 1210 | s_mov_b32 s58, 0 // s_win = 0 (default: raced no ticket) |
1079 | 1211 | occ_sample s55, s56 // s55=occ_A in [0,G], s56=occ_B in [0,FN] (clob s60,s61) |
1080 | 1212 | s_cmp_lt_u32 s55, CTRL_LOW |
1081 | 1213 | s_cbranch_scc0 .Lcmp_try_b |
1082 | 1214 | s_mov_b32 s57, 0 // dir 0: compute -> A-feed |
1083 | 1215 | try_gate 0, s58 // s58 = s_win (single-winner per (dir,epoch)) |
1084 | 1216 | conv_apply NCOMP_SLOT, NAFEED_SLOT, -(NFV-VLEAN), 32 |
1085 | | - s_branch .Lcmp_quiesce |
| 1217 | + s_branch .Lcmp_cooldn |
1086 | 1218 | .Lcmp_try_b: |
1087 | 1219 | s_cmp_lt_u32 s56, CTRL_LOW |
1088 | | - s_cbranch_scc0 .Lcmp_quiesce |
| 1220 | + s_cbranch_scc0 .Lcmp_cooldn |
1089 | 1221 | s_mov_b32 s57, 1 // dir 1: compute -> B-feed |
1090 | 1222 | try_gate 1, s58 |
1091 | 1223 | conv_apply NCOMP_SLOT, NBFEED_SLOT, -(NFV-VLEAN), 32 |
| 1224 | +.endif |
| 1225 | +.Lcmp_cooldn: |
| 1226 | +.if CONV_COOLDOWN > 0 |
| 1227 | + s_cmp_eq_u32 s66, 0 // Task 4: decrement once per epoch (saturating) |
| 1228 | + s_cbranch_scc1 .Lcmp_quiesce |
| 1229 | + s_sub_i32 s66, s66, 1 |
| 1230 | +.endif |
1092 | 1231 | .Lcmp_quiesce: |
1093 | 1232 | // ORDERING (SPEC 3.4 decision 2): the commit above fully completed (role-slot CAS + reservation + |
1094 | 1233 | // s_alloc_vgpr) BEFORE this QUIESCE_CNT bump -- the bump is the snapshot handshake the claimer reads. |
1095 | 1234 | lds_fetch_add s61, QUIESCE_CNT_OFF, 1 // exactly one bump per non-claimer wave / super-tile |
1096 | 1235 | .endif |
| 1236 | +.if DSWS2_CONV |
| 1237 | + s_branch .Ldispatch |
| 1238 | +.else |
1097 | 1239 | s_branch .Lcompute_follow // this super-tile's compute drained -> re-check epoch/terminal |
| 1240 | +.endif |
| 1241 | + |
| 1242 | +.if DSWS2_CONV |
| 1243 | +// ============================================================================================ |
| 1244 | +// Universal role dispatcher (Task 3): scalar-only trampoline. Reads the role register s59 |
| 1245 | +// (seeded at entry / flipped by conv_apply on a role conversion) and branches to the matching |
| 1246 | +// role's per-epoch _follow loop. Lands on _follow (NOT _alloc/_init): the wave's VGPR footprint |
| 1247 | +// is already correct (seed or conv_apply set it) and INIT already ran once -- re-entering |
| 1248 | +// _alloc would wrongly resize, and _init would deadlock on the already-consumed INITFLAG. |
| 1249 | +// s35 (last-seen-epoch) is untouched here, which is what makes a re-dispatched wave wait for |
| 1250 | +// the NEXT epoch at the top of its new role's _follow loop. Scalar-only (s59 read + s_branch) -> |
| 1251 | +// adds ZERO OOR/VGPR exposure. |
| 1252 | +// ============================================================================================ |
| 1253 | +.Ldispatch: |
| 1254 | + s_cmp_eq_u32 s59, NCOMP_SLOT |
| 1255 | + s_cbranch_scc1 .Lcompute_follow |
| 1256 | + s_cmp_eq_u32 s59, NAFEED_SLOT |
| 1257 | + s_cbranch_scc1 .Lafeed_follow |
| 1258 | + s_branch .Lbfeed_follow |
| 1259 | +.endif |
1098 | 1260 |
|
1099 | 1261 | // ---- A7 role-agnostic terminal (followers): retire. (Claimer retires via .Lclaimer_terminal.) ---- |
1100 | 1262 | .Lretire: |
|
0 commit comments