structure.c (28130B)
1 /* Wasm CGTarget structurer. 2 * 3 * Walks the WIR list produced by CG record callbacks and rewrites it so 4 * every label is bound to a structured Wasm scope. Free WIR_LABELs (those 5 * placed via wasm_label_place outside any CG scope's break/continue role) 6 * are wrapped in synthetic SCOPE_BLOCK (forward jumps) or SCOPE_LOOP 7 * (backward jumps) records; jumps to the formerly-free labels resolve via 8 * the existing scope-bound br_to_label machinery in emit.c. 9 * 10 * Switch islands — the C/toy `switch` shape `JUMP dispatch; case bodies; 11 * LABEL dispatch; selector; WIR_SWITCH; LABEL end` — are pre-processed 12 * by unroll_switch_islands, which reorders the WIR to put selector + 13 * SWITCH first and the case bodies after. That conversion turns the 14 * case labels into forward refs the structurer handles uniformly with 15 * other forward gotos. 16 * 17 * Diagnostics (hard wfail): 18 * - irreducible: a label has both forward and backward predecessors 19 * (multi-entry loop or hybrid; v1 declines node splitting). 20 * - cross-scope: a free label sits at scope depth D, but a predecessor 21 * sits inside an extra CG scope; the synthetic block's start cannot 22 * lift across the scope boundary cleanly. */ 23 24 #include <stdarg.h> 25 #include <string.h> 26 27 #include "arch/wasm/internal.h" 28 #include "core/heap.h" 29 30 static _Noreturn void sfail(WTarget* t, const char* fmt, ...) { 31 va_list ap; 32 SrcLoc l = {0, 0, 0}; 33 va_start(ap, fmt); 34 if (t->cur_fn_desc) l = t->cur_fn_desc->loc; 35 compiler_panicv(t->c, l, fmt, ap); 36 } 37 38 /* Per-free-label working state. */ 39 typedef struct WSLbl { 40 Label label; 41 u32 wir_idx; /* WIR_LABEL position */ 42 u32 scope_depth; /* CG-scope nesting depth at the label */ 43 /* Forward predecessor stats: min adjusted start (so the synthetic block 44 * opens at the same scope depth as L). UINT32_MAX = no forward pred. */ 45 u32 fwd_start_idx; 46 u32 nfwd; 47 /* Backward predecessor stats: max adjusted end (exclusive) for the 48 * synthetic loop close. UINT32_MAX = no backward pred. */ 49 u32 bwd_end_idx; 50 u32 nbwd; 51 /* After pass_decide: actual synthetic scope range. open_idx is mutable 52 * by merge_ranges (extended backward to nest overlapping forward blocks); 53 * close_idx is fixed at the label's natural position so jumps to the 54 * label still land at the right place. */ 55 u32 open_idx; 56 u32 close_idx; 57 u8 is_loop; 58 u8 valid; /* 1 if structurer will wrap this label */ 59 u8 pad[2]; 60 } WSLbl; 61 62 /* Event inserted at a WIR position. Sorted primarily by wir_idx; within the 63 * same wir_idx, closes fire before opens. The pair_idx field carries the 64 * partner event's wir_idx so we can break ties: at a shared OPEN wir_idx, 65 * the outer (larger pair_idx = later close) opens first; at a shared CLOSE 66 * wir_idx, the inner (larger pair_idx = later open) closes first. */ 67 typedef enum WSEvKind { 68 WSEV_CLOSE_LOOP = 0, 69 WSEV_CLOSE_BLOCK = 1, 70 WSEV_OPEN_BLOCK = 2, 71 WSEV_OPEN_LOOP = 3, 72 } WSEvKind; 73 74 typedef struct WSEvent { 75 u32 wir_idx; 76 u32 pair_idx; 77 u8 kind; /* WSEvKind */ 78 u8 pad[3]; 79 u32 scope_id; 80 Label label; /* break (BLOCK) or continue (LOOP) */ 81 } WSEvent; 82 83 /* Per-idx scope chain entry. */ 84 typedef struct WSScopeFrame { 85 u32 scope_id; 86 u32 open_idx; 87 u32 depth; /* 1-based */ 88 } WSScopeFrame; 89 90 typedef struct WSCtx { 91 WTarget* t; 92 WSLbl* lbls; 93 u32 nlbls; 94 u32 lbls_cap; 95 /* Per-WIR-idx scope depth (immediately AT this index — for SCOPE_OPEN, 96 * the depth AFTER pushing). */ 97 u32* depth_at; 98 u32 ndepth; 99 /* Stack of currently-open scopes during the WIR walk; recycled per 100 * pass since CG scopes never go above 32. */ 101 WSScopeFrame stack[64]; 102 u32 nstack; 103 WSEvent* events; 104 u32 nevents; 105 u32 events_cap; 106 } WSCtx; 107 108 /* ---- label index ---- */ 109 110 static WSLbl* find_lbl(WSCtx* x, Label l) { 111 for (u32 i = 0; i < x->nlbls; ++i) { 112 if (x->lbls[i].label == l) return &x->lbls[i]; 113 } 114 return NULL; 115 } 116 117 static WSLbl* push_lbl(WSCtx* x, Label l) { 118 Heap* h = x->t->c->ctx->heap; 119 if (x->nlbls == x->lbls_cap) { 120 u32 nc = x->lbls_cap ? x->lbls_cap * 2u : 8u; 121 void* p = h->realloc(h, x->lbls, sizeof(WSLbl) * x->lbls_cap, 122 sizeof(WSLbl) * nc, _Alignof(WSLbl)); 123 if (!p) sfail(x->t, "wasm: out of memory in structurer"); 124 x->lbls = (WSLbl*)p; 125 x->lbls_cap = nc; 126 } 127 WSLbl* L = &x->lbls[x->nlbls++]; 128 memset(L, 0, sizeof *L); 129 L->label = l; 130 L->fwd_start_idx = UINT32_MAX; 131 L->bwd_end_idx = UINT32_MAX; 132 L->open_idx = UINT32_MAX; 133 L->close_idx = UINT32_MAX; 134 return L; 135 } 136 137 /* ---- pass 1: scope-depth map + free-label discovery ---- */ 138 139 static void pass_scope_depth(WSCtx* x) { 140 WTarget* t = x->t; 141 Heap* h = t->c->ctx->heap; 142 x->depth_at = 143 (u32*)h->alloc(h, sizeof(u32) * (t->nwir ? t->nwir : 1u), _Alignof(u32)); 144 if (!x->depth_at) sfail(t, "wasm: out of memory in structurer"); 145 x->ndepth = t->nwir; 146 x->nstack = 0; 147 for (u32 i = 0; i < t->nwir; ++i) { 148 WIR* w = &t->wir[i]; 149 if (w->op == WIR_SCOPE_OPEN) { 150 if (x->nstack >= 64u) sfail(t, "wasm: scope nesting too deep"); 151 x->stack[x->nstack].scope_id = w->scope_id; 152 x->stack[x->nstack].open_idx = i; 153 x->stack[x->nstack].depth = x->nstack + 1u; 154 x->nstack++; 155 } 156 x->depth_at[i] = x->nstack; 157 if (w->op == WIR_SCOPE_CLOSE) { 158 if (x->nstack == 0) 159 sfail(t, "wasm: structurer saw SCOPE_CLOSE with empty stack"); 160 x->nstack--; 161 /* Depth AT a CLOSE is the depth INSIDE the scope (we haven't popped 162 * yet at the time the record sits in WIR). The pop above is for the 163 * NEXT idx. Restore. */ 164 x->depth_at[i] = x->nstack + 1u; 165 } 166 if (w->op == WIR_LABEL) { 167 WLabel* lbl = &t->labels[w->labels[0] - 1u]; 168 if (lbl->kind == WLBL_FORWARD || lbl->kind == WLBL_UNBOUND) { 169 WSLbl* L = find_lbl(x, w->labels[0]); 170 if (!L) L = push_lbl(x, w->labels[0]); 171 L->wir_idx = i; 172 L->scope_depth = x->depth_at[i]; 173 } 174 } 175 } 176 if (x->nstack != 0) 177 sfail(t, "wasm: structurer finished with %u scopes still open", x->nstack); 178 } 179 180 /* ---- pass 2: locate the smallest "outside" idx for a pred at depth `pd` 181 * whose target sits at depth `ld`. Walks backward through WIR's scope 182 * transitions to find the open_idx of the outermost CG scope that 183 * contains the pred but not the target. ---- */ 184 static u32 lift_to_label_depth(WSCtx* x, u32 pred_idx, u32 ld) { 185 WTarget* t = x->t; 186 u32 cur = x->depth_at[pred_idx]; 187 /* We need cur to drop to ld. Walk backward; each SCOPE_OPEN we cross 188 * (going up the depth) corresponds to an extra scope above the target's 189 * depth. We stop AT the outermost extra SCOPE_OPEN. */ 190 if (cur <= ld) return pred_idx; 191 u32 i = pred_idx; 192 u32 best = pred_idx; 193 while (i > 0 && cur > ld) { 194 --i; 195 if (t->wir[i].op == WIR_SCOPE_OPEN) { 196 cur--; 197 if (cur == ld) { 198 best = i; /* place synthetic OPEN right before this CG OPEN */ 199 break; 200 } 201 } else if (t->wir[i].op == WIR_SCOPE_CLOSE) { 202 cur++; 203 } 204 } 205 return best; 206 } 207 208 /* Symmetric: for a backward pred at idx pred_idx targeting label at depth ld, 209 * return the WIR position (exclusive end) where the synthetic LOOP must 210 * close so that it sits at the same scope depth as the label. */ 211 static u32 drop_to_label_depth_after(WSCtx* x, u32 pred_idx, u32 ld) { 212 WTarget* t = x->t; 213 u32 cur = x->depth_at[pred_idx]; 214 /* Walk forward across closes until we drop to ld. The synthetic close 215 * happens AFTER the SCOPE_CLOSE that takes us back to ld. */ 216 if (cur <= ld) return pred_idx + 1u; 217 u32 i = pred_idx; 218 while (i < t->nwir && cur > ld) { 219 ++i; 220 if (i >= t->nwir) break; 221 if (t->wir[i].op == WIR_SCOPE_OPEN) { 222 cur++; 223 } else if (t->wir[i].op == WIR_SCOPE_CLOSE) { 224 cur--; 225 if (cur == ld) { 226 return i + 1u; 227 } 228 } 229 } 230 /* Couldn't drop — back-edge spans out of the function body? Treat as 231 * unstructurable. */ 232 return UINT32_MAX; 233 } 234 235 /* ---- pass 3: record pred contributions for each free label. Walk WIR 236 * and for each JUMP/CMP_BRANCH/SWITCH that targets a tracked free 237 * label, update fwd_start_idx / bwd_end_idx / nfwd / nbwd. ---- */ 238 static void verify_well_nested_fwd(WSCtx* x, u32 start, u32 end, u32 ld) { 239 /* Every WIR index in [start, end) must have scope depth >= ld so the 240 * synthetic block can sit at L's depth without crossing a CG-scope 241 * boundary outward. */ 242 for (u32 i = start; i < end; ++i) { 243 if (x->depth_at[i] < ld) { 244 sfail(x->t, 245 "wasm: forward jump crosses out of structured scope at WIR[%u]; " 246 "kit's CFG structurer does not split scopes in v1", 247 i); 248 } 249 } 250 } 251 252 static void note_pred(WSCtx* x, u32 pred_idx, Label tgt) { 253 WSLbl* L = find_lbl(x, tgt); 254 if (!L) return; 255 if (pred_idx < L->wir_idx) { 256 /* Forward pred. */ 257 u32 lifted = lift_to_label_depth(x, pred_idx, L->scope_depth); 258 verify_well_nested_fwd(x, lifted, L->wir_idx, L->scope_depth); 259 if (lifted < L->fwd_start_idx) L->fwd_start_idx = lifted; 260 L->nfwd++; 261 } else if (pred_idx > L->wir_idx) { 262 /* Backward pred. */ 263 u32 dropped = drop_to_label_depth_after(x, pred_idx, L->scope_depth); 264 if (dropped == UINT32_MAX) { 265 sfail(x->t, 266 "wasm: backward jump at WIR[%u] to label outside function body; " 267 "structurer cannot place enclosing loop", 268 pred_idx); 269 } 270 if (L->bwd_end_idx == UINT32_MAX || dropped > L->bwd_end_idx) 271 L->bwd_end_idx = dropped; 272 L->nbwd++; 273 } 274 } 275 276 /* Switch-island reorder. 277 * 278 * Frontends (C, toy) emit `switch` as: 279 * JUMP dispatch // forward, to skip past case bodies on entry 280 * LABEL case_1; body; JUMP end 281 * LABEL case_2; body; JUMP end 282 * LABEL dispatch 283 * selector 284 * WIR_SWITCH cases: [case_1, case_2], default: end 285 * LABEL end 286 * 287 * The case labels are placed BEFORE the WIR_SWITCH that targets them — a 288 * backward edge in WIR order that the natural structurer cannot wrap as 289 * forward blocks. We rewrite the WIR to move the dispatch (selector + 290 * SWITCH) up to the position of the original JUMP and let the case 291 * bodies follow: 292 * 293 * selector 294 * WIR_SWITCH cases: [case_1, case_2], default: end 295 * LABEL case_1; body; JUMP end 296 * LABEL case_2; body; JUMP end 297 * LABEL end 298 * 299 * Now case_i is forward from SWITCH and the structurer wraps it in a 300 * synthetic SCOPE_BLOCK like any other forward goto. The JUMP and the 301 * dispatch LABEL are dropped (the dispatch label is no longer reached). 302 * 303 * The pattern is detected by scanning forward from each WIR_JUMP. We 304 * support multiple non-nested switch islands per function and stop on 305 * the first intervening terminator that breaks the dispatch shape. */ 306 typedef struct WSIsland { 307 u32 j; /* JUMP dispatch idx */ 308 u32 d; /* LABEL dispatch idx */ 309 u32 s; /* WIR_SWITCH idx */ 310 } WSIsland; 311 312 static WIR* wir_append(WTarget* t, WIR** out, u32* nout, u32* cap); 313 314 /* One pass of switch-island detection + reordering. Returns the number of 315 * islands rewritten (0 = nothing to do). Nested switches need multiple passes: 316 * the outer JUMP-to-dispatch sits before the inner switch's JUMP-to-dispatch 317 * in WIR order, so the outer scan advances past the inner JUMP after recording 318 * the outer island. Re-scanning the rewritten WIR brings the now-exposed inner 319 * island into view. */ 320 static int unroll_switch_islands_once(WSCtx* x) { 321 WTarget* t = x->t; 322 Heap* h = t->c->ctx->heap; 323 WSIsland islands[16]; 324 u32 nislands = 0; 325 u32 scan = 0; 326 while (scan < t->nwir) { 327 if (t->wir[scan].op != WIR_JUMP) { 328 scan++; 329 continue; 330 } 331 Label disp = t->wir[scan].labels[0]; 332 if (disp == LABEL_NONE || disp - 1u >= t->nlabels) { 333 scan++; 334 continue; 335 } 336 WLabel* dlbl = &t->labels[disp - 1u]; 337 if (dlbl->kind != WLBL_FORWARD || !dlbl->placed || 338 dlbl->wir_index <= scan) { 339 scan++; 340 continue; 341 } 342 /* Find WIR_SWITCH just past dispatch label; bail on intervening 343 * terminator or scope boundary. The C/toy frontends emit the 344 * selector expression as a small straight-line sequence between 345 * LABEL dispatch and WIR_SWITCH, with no SCOPE_OPEN/CLOSE — those 346 * only appear at switch entry/exit, never around the dispatcher. 347 * Rejecting SCOPE_OPEN here keeps a second pass from mistaking a 348 * for-loop body label (which sits just before a `[switch open]`) 349 * for a switch dispatch label. */ 350 u32 sw_i = UINT32_MAX; 351 for (u32 k = dlbl->wir_index + 1u; k < t->nwir; ++k) { 352 if (t->wir[k].op == WIR_SWITCH) { 353 sw_i = k; 354 break; 355 } 356 if (t->wir[k].op == WIR_JUMP || t->wir[k].op == WIR_CMP_BRANCH || 357 t->wir[k].op == WIR_RET || t->wir[k].op == WIR_UNREACHABLE || 358 t->wir[k].op == WIR_SCOPE_OPEN || t->wir[k].op == WIR_SCOPE_CLOSE) 359 break; 360 } 361 if (sw_i == UINT32_MAX) { 362 scan++; 363 continue; 364 } 365 if (nislands >= 16u) 366 sfail(t, 367 "wasm: too many switch islands per function (max 16 supported in " 368 "v1)"); 369 islands[nislands].j = scan; 370 islands[nislands].d = dlbl->wir_index; 371 islands[nislands].s = sw_i; 372 nislands++; 373 scan = sw_i + 1u; 374 } 375 if (nislands == 0) return 0; 376 377 /* Build a rewritten WIR list. For each island, emit pre + (selector + 378 * SWITCH) + (case bodies). Then everything after the last island's 379 * SWITCH is copied through. */ 380 WIR* new_wir = NULL; 381 u32 new_n = 0; 382 u32 new_cap = 0; 383 u32 cursor = 0; 384 for (u32 ii = 0; ii < nislands; ++ii) { 385 WSIsland* I = &islands[ii]; 386 /* pre-island: WIR[cursor .. I->j) */ 387 for (u32 k = cursor; k < I->j; ++k) { 388 WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); 389 *w = t->wir[k]; 390 } 391 /* selector + SWITCH: WIR[I->d+1 .. I->s] */ 392 for (u32 k = I->d + 1u; k <= I->s; ++k) { 393 WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); 394 *w = t->wir[k]; 395 } 396 /* case bodies: WIR[I->j+1 .. I->d) — drops the JUMP at j and LABEL at d */ 397 for (u32 k = I->j + 1u; k < I->d; ++k) { 398 WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); 399 *w = t->wir[k]; 400 } 401 cursor = I->s + 1u; 402 } 403 /* trailing region after the last island */ 404 for (u32 k = cursor; k < t->nwir; ++k) { 405 WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); 406 *w = t->wir[k]; 407 } 408 409 if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); 410 t->wir = new_wir; 411 t->nwir = new_n; 412 t->wir_cap = new_cap; 413 414 /* Refresh WLabel.wir_index from the new layout so any downstream 415 * consumer of t->labels[].wir_index sees the rewritten positions. 416 * (pass_scope_depth re-discovers free-label positions on its own walk.) */ 417 for (u32 k = 0; k < t->nwir; ++k) { 418 if (t->wir[k].op != WIR_LABEL) continue; 419 Label l = t->wir[k].labels[0]; 420 if (l != LABEL_NONE && l - 1u < t->nlabels) t->labels[l - 1u].wir_index = k; 421 } 422 return (int)nislands; 423 } 424 425 /* Re-run the single-pass unroller to a fixed point so nested switches all 426 * get reordered. Each pass reorders at most the outermost island in a chain 427 * of nested switches; the next pass exposes the next inner one. The loop 428 * terminates because every rewritten island drops one JUMP-to-dispatch from 429 * the WIR (and a function has finitely many). */ 430 static void unroll_switch_islands(WSCtx* x) { 431 while (unroll_switch_islands_once(x) > 0) { /* re-scan after each rewrite */ 432 } 433 } 434 435 /* For-loop de-rotation. 436 * 437 * The C frontend lowers `for (init; cond; step) body` in a single pass, so 438 * the step clause (which appears before the body in source) is emitted into 439 * a block placed BEFORE the body, with the body jumping back to it: 440 * 441 * LABEL L_top // header 442 * <cond>; CMP_BRANCH L_end // (cond optional) 443 * JUMP L_body // skip the step on entry 444 * LABEL L_step // continue target 445 * <step> 446 * JUMP L_top // genuine loop back-edge 447 * LABEL L_body 448 * <body> 449 * JUMP L_step // body -> step (continue path) 450 * LABEL L_end 451 * 452 * In WIR textual order the body's `JUMP L_step` is a backward edge, so the 453 * structurer misclassifies L_step as a loop header and emits a second, 454 * unintended `loop` — the program spins forever. The CFG is reducible; only 455 * the block layout is rotated. We restore a structure-compatible order by 456 * moving the step block to AFTER the body, mirroring how 457 * unroll_switch_islands reorders the switch shape: 458 * 459 * LABEL L_top 460 * <cond>; CMP_BRANCH L_end 461 * LABEL L_body // JUMP L_body dropped; falls through 462 * <body> 463 * JUMP L_step // now a forward edge 464 * LABEL L_step 465 * <step> 466 * JUMP L_top // sole back-edge -> L_top is the loop 467 * LABEL L_end 468 * 469 * The step block ends in an unconditional `JUMP L_top` (no fall-through out) 470 * and is entered only via `JUMP L_step`, so relocating it preserves 471 * semantics. Nested for-loops live entirely inside the body region and are 472 * handled by re-scanning to a fixed point. */ 473 static int derotate_one_for_loop(WSCtx* x) { 474 WTarget* t = x->t; 475 Heap* h = t->c->ctx->heap; 476 for (u32 j = 0; j < t->nwir; ++j) { 477 if (t->wir[j].op != WIR_JUMP) continue; 478 /* JUMP L_body, with L_body a forward label. */ 479 Label xb = t->wir[j].labels[0]; 480 if (xb == LABEL_NONE || xb - 1u >= t->nlabels) continue; 481 WLabel* lx = &t->labels[xb - 1u]; 482 if (!lx->placed || lx->wir_index <= j) continue; 483 /* Immediately followed by LABEL L_step. */ 484 if (j + 1u >= t->nwir || t->wir[j + 1u].op != WIR_LABEL) continue; 485 Label s = t->wir[j + 1u].labels[0]; 486 if (s == LABEL_NONE || s == xb) continue; 487 /* Scan the step block for `JUMP L_top; LABEL L_body`, L_top backward. */ 488 u32 jt = UINT32_MAX; 489 for (u32 m = j + 2u; m + 1u < t->nwir; ++m) { 490 u8 mop = t->wir[m].op; 491 if (mop == WIR_RET || mop == WIR_UNREACHABLE) break; 492 if (mop != WIR_JUMP) continue; 493 Label cand = t->wir[m].labels[0]; 494 if (cand == LABEL_NONE || cand - 1u >= t->nlabels) continue; 495 WLabel* lc = &t->labels[cand - 1u]; 496 if (lc->placed && lc->wir_index < j && t->wir[m + 1u].op == WIR_LABEL && 497 t->wir[m + 1u].labels[0] == xb) { 498 jt = m; 499 break; 500 } 501 } 502 if (jt == UINT32_MAX) continue; 503 /* k = last `JUMP L_step` (the body's closing/continue edges all target 504 * L_step; the textually-last one closes the body, just before L_end). */ 505 u32 k = UINT32_MAX; 506 for (u32 m = jt + 1u; m < t->nwir; ++m) { 507 if (t->wir[m].op == WIR_JUMP && t->wir[m].labels[0] == s) k = m; 508 } 509 if (k == UINT32_MAX || k <= jt) continue; 510 511 /* Rebuild: [0..j) drop JUMP at j, [jt+1..k] body, [j+1..jt] step, 512 * [k+1..nwir). */ 513 WIR* nw = NULL; 514 u32 nn = 0, ncap = 0; 515 for (u32 m = 0; m < j; ++m) *wir_append(t, &nw, &nn, &ncap) = t->wir[m]; 516 for (u32 m = jt + 1u; m <= k; ++m) 517 *wir_append(t, &nw, &nn, &ncap) = t->wir[m]; 518 for (u32 m = j + 1u; m <= jt; ++m) 519 *wir_append(t, &nw, &nn, &ncap) = t->wir[m]; 520 for (u32 m = k + 1u; m < t->nwir; ++m) 521 *wir_append(t, &nw, &nn, &ncap) = t->wir[m]; 522 523 if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); 524 t->wir = nw; 525 t->nwir = nn; 526 t->wir_cap = ncap; 527 for (u32 m = 0; m < t->nwir; ++m) { 528 if (t->wir[m].op != WIR_LABEL) continue; 529 Label l = t->wir[m].labels[0]; 530 if (l != LABEL_NONE && l - 1u < t->nlabels) 531 t->labels[l - 1u].wir_index = m; 532 } 533 return 1; 534 } 535 return 0; 536 } 537 538 static void derotate_for_loops(WSCtx* x) { 539 while (derotate_one_for_loop(x)) { /* re-scan after each reorder */ 540 } 541 } 542 543 static void pass_preds(WSCtx* x) { 544 WTarget* t = x->t; 545 for (u32 i = 0; i < t->nwir; ++i) { 546 WIR* w = &t->wir[i]; 547 switch (w->op) { 548 case WIR_JUMP: 549 case WIR_CMP_BRANCH: 550 if (w->labels[0] != LABEL_NONE) note_pred(x, i, w->labels[0]); 551 break; 552 case WIR_SWITCH: 553 if (w->labels[0] != LABEL_NONE) note_pred(x, i, w->labels[0]); 554 for (u32 c = 0; c < w->switch_ncases; ++c) { 555 if (w->switch_cases[c].label != LABEL_NONE) 556 note_pred(x, i, w->switch_cases[c].label); 557 } 558 break; 559 default: 560 break; 561 } 562 } 563 } 564 565 /* ---- pass 4: produce per-label decisions. ---- */ 566 static void pass_decide(WSCtx* x) { 567 WTarget* t = x->t; 568 for (u32 i = 0; i < x->nlbls; ++i) { 569 WSLbl* L = &x->lbls[i]; 570 if (L->nfwd == 0 && L->nbwd == 0) { 571 /* Dead label — placed but never branched to. Drop quietly. */ 572 L->valid = 0; 573 continue; 574 } 575 if (L->nfwd > 0 && L->nbwd > 0) { 576 sfail(t, 577 "wasm: label has both forward and backward gotos; kit's CFG " 578 "structurer does not split hybrid labels in v1"); 579 } 580 L->valid = 1; 581 if (L->nfwd > 0) { 582 L->is_loop = 0; 583 L->open_idx = L->fwd_start_idx; 584 L->close_idx = L->wir_idx; 585 } else { 586 L->is_loop = 1; 587 L->open_idx = L->wir_idx; 588 L->close_idx = L->bwd_end_idx; 589 } 590 } 591 } 592 593 /* ---- pass 5: emit event list and validate well-nesting. ---- */ 594 595 static void push_event(WSCtx* x, u32 wir_idx, u32 pair_idx, u8 kind, 596 u32 scope_id, Label label) { 597 Heap* h = x->t->c->ctx->heap; 598 if (x->nevents == x->events_cap) { 599 u32 nc = x->events_cap ? x->events_cap * 2u : 16u; 600 void* p = h->realloc(h, x->events, sizeof(WSEvent) * x->events_cap, 601 sizeof(WSEvent) * nc, _Alignof(WSEvent)); 602 if (!p) sfail(x->t, "wasm: out of memory in structurer"); 603 x->events = (WSEvent*)p; 604 x->events_cap = nc; 605 } 606 WSEvent* e = &x->events[x->nevents++]; 607 e->wir_idx = wir_idx; 608 e->pair_idx = pair_idx; 609 e->kind = kind; 610 e->scope_id = scope_id; 611 e->label = label; 612 } 613 614 static int ev_cmp(const WSEvent* a, const WSEvent* b) { 615 if (a->wir_idx != b->wir_idx) return a->wir_idx < b->wir_idx ? -1 : 1; 616 /* At a tie: closes fire before opens (so we close existing scopes before 617 * opening new ones). CLOSE kinds are 0..1, OPEN kinds are 2..3. */ 618 int a_close = a->kind < WSEV_OPEN_BLOCK; 619 int b_close = b->kind < WSEV_OPEN_BLOCK; 620 if (a_close != b_close) return a_close ? -1 : 1; 621 if (a_close) { 622 /* CLOSE: innermost first. pair_idx = open_idx; the more-recently-opened 623 * scope has a LATER open_idx, so larger pair_idx closes first. */ 624 if (a->pair_idx != b->pair_idx) return a->pair_idx > b->pair_idx ? -1 : 1; 625 return 0; 626 } 627 /* OPEN: outer first. pair_idx = close_idx; the larger range (later close) 628 * encloses the smaller, so larger pair_idx opens first. */ 629 if (a->pair_idx != b->pair_idx) return a->pair_idx > b->pair_idx ? -1 : 1; 630 return 0; 631 } 632 633 static void ev_sort(WSEvent* arr, u32 n) { 634 /* Small n — insertion sort. */ 635 for (u32 i = 1; i < n; ++i) { 636 WSEvent k = arr[i]; 637 u32 j = i; 638 while (j > 0 && ev_cmp(&arr[j - 1u], &k) > 0) { 639 arr[j] = arr[j - 1u]; 640 --j; 641 } 642 arr[j] = k; 643 } 644 } 645 646 /* Resolve overlapping synthetic ranges into a properly-nested set. 647 * 648 * Close positions are FIXED — they correspond to the label's natural WIR 649 * placement (for forward labels) or the last back-edge end (for loop 650 * headers). Moving a close would change where a br to the label lands. 651 * 652 * So we only adjust OPEN positions, pushing them backward as needed: if 653 * range A's close happens inside range B (a_o < b_o < a_c < b_c), pull 654 * B's open back to A's open so A nests inside B (both open at a_o; A's 655 * inner close at a_c fires before B's outer close at b_c). 656 * 657 * Iterate to a fixed point since extending one range can introduce new 658 * overlaps with a third. The number of distinct opens is bounded by the 659 * label count, so iteration terminates in O(nlbls^2). */ 660 static void merge_ranges(WSCtx* x) { 661 for (;;) { 662 int changed = 0; 663 for (u32 i = 0; i < x->nlbls; ++i) { 664 if (!x->lbls[i].valid) continue; 665 for (u32 j = 0; j < x->nlbls; ++j) { 666 if (j == i || !x->lbls[j].valid) continue; 667 WSLbl* a = &x->lbls[i]; 668 WSLbl* b = &x->lbls[j]; 669 u32 a_o = a->open_idx, a_c = a->close_idx; 670 u32 b_o = b->open_idx, b_c = b->close_idx; 671 /* Disjoint or nested — fine. */ 672 if (a_c <= b_o || b_c <= a_o) continue; 673 if (a_o <= b_o && b_c <= a_c) continue; 674 if (b_o <= a_o && a_c <= b_c) continue; 675 /* Overlap. Two cases: 676 * a_o < b_o < a_c < b_c -> pull b's open back to a_o 677 * b_o < a_o < b_c < a_c -> handled by the j-then-i symmetric 678 * iteration. 679 */ 680 if (a_o < b_o && b_o < a_c && a_c < b_c) { 681 b->open_idx = a_o; 682 changed = 1; 683 } 684 } 685 } 686 if (!changed) break; 687 } 688 } 689 690 static void pass_events(WSCtx* x) { 691 WTarget* t = x->t; 692 for (u32 i = 0; i < x->nlbls; ++i) { 693 WSLbl* L = &x->lbls[i]; 694 if (!L->valid) continue; 695 u32 scope_id = ++t->next_scope_id; 696 if (L->is_loop) { 697 push_event(x, L->open_idx, L->close_idx, WSEV_OPEN_LOOP, scope_id, 698 L->label); 699 push_event(x, L->close_idx, L->open_idx, WSEV_CLOSE_LOOP, scope_id, 700 L->label); 701 } else { 702 push_event(x, L->open_idx, L->close_idx, WSEV_OPEN_BLOCK, scope_id, 703 L->label); 704 push_event(x, L->close_idx, L->open_idx, WSEV_CLOSE_BLOCK, scope_id, 705 L->label); 706 } 707 /* Re-bind the WLabel. */ 708 WLabel* wl = &t->labels[L->label - 1u]; 709 wl->scope_id = scope_id; 710 wl->kind = L->is_loop ? WLBL_SCOPE_CONT : WLBL_SCOPE_BREAK; 711 } 712 ev_sort(x->events, x->nevents); 713 } 714 715 /* ---- pass 6: rebuild the WIR list, splicing in synthetic scope opens 716 * and closes at their event positions. ---- */ 717 718 static WIR* wir_append(WTarget* t, WIR** out, u32* nout, u32* cap) { 719 Heap* h = t->c->ctx->heap; 720 if (*nout == *cap) { 721 u32 nc = *cap ? *cap * 2u : 64u; 722 void* p = h->realloc(h, *out, sizeof(WIR) * *cap, sizeof(WIR) * nc, 723 _Alignof(WIR)); 724 if (!p) sfail(t, "wasm: out of memory in structurer"); 725 *out = (WIR*)p; 726 *cap = nc; 727 } 728 WIR* w = &(*out)[(*nout)++]; 729 memset(w, 0, sizeof *w); 730 return w; 731 } 732 733 static void emit_synthetic_scope(WTarget* t, WIR** out, u32* nout, u32* cap, 734 u8 ev_kind, u32 scope_id) { 735 WIR* w = wir_append(t, out, nout, cap); 736 if (ev_kind == WSEV_OPEN_BLOCK || ev_kind == WSEV_OPEN_LOOP) { 737 w->op = WIR_SCOPE_OPEN; 738 w->cgop = (ev_kind == WSEV_OPEN_LOOP) ? SCOPE_LOOP : SCOPE_BLOCK; 739 } else { 740 w->op = WIR_SCOPE_CLOSE; 741 } 742 w->scope_id = scope_id; 743 w->dst = REG_NONE; 744 } 745 746 static void pass_rewrite(WSCtx* x) { 747 WTarget* t = x->t; 748 WIR* new_wir = NULL; 749 u32 new_n = 0; 750 u32 new_cap = 0; 751 u32 ev_i = 0; 752 for (u32 i = 0; i <= t->nwir; ++i) { 753 /* Fire all events whose wir_idx == i (closes first, then opens). */ 754 while (ev_i < x->nevents && x->events[ev_i].wir_idx == i) { 755 emit_synthetic_scope(t, &new_wir, &new_n, &new_cap, x->events[ev_i].kind, 756 x->events[ev_i].scope_id); 757 ev_i++; 758 } 759 if (i == t->nwir) break; 760 WIR* src = &t->wir[i]; 761 WIR* dst = wir_append(t, &new_wir, &new_n, &new_cap); 762 *dst = *src; 763 } 764 if (ev_i != x->nevents) 765 sfail(t, "wasm: structurer left %u events unplaced", x->nevents - ev_i); 766 /* Swap. */ 767 Heap* h = t->c->ctx->heap; 768 if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); 769 t->wir = new_wir; 770 t->nwir = new_n; 771 t->wir_cap = new_cap; 772 } 773 774 /* ---- entry point ---- */ 775 776 void wasm_structurize(WTarget* t) { 777 if (t->nwir == 0) return; 778 Heap* h = t->c->ctx->heap; 779 WSCtx x; 780 memset(&x, 0, sizeof x); 781 x.t = t; 782 unroll_switch_islands(&x); 783 derotate_for_loops(&x); 784 pass_scope_depth(&x); 785 pass_preds(&x); 786 pass_decide(&x); 787 if (x.nlbls > 0) { 788 int any = 0; 789 for (u32 i = 0; i < x.nlbls; ++i) 790 if (x.lbls[i].valid) { 791 any = 1; 792 break; 793 } 794 if (any) { 795 merge_ranges(&x); 796 pass_events(&x); 797 pass_rewrite(&x); 798 } 799 } 800 if (x.depth_at) h->free(h, x.depth_at, sizeof(u32) * x.ndepth); 801 if (x.lbls) h->free(h, x.lbls, sizeof(WSLbl) * x.lbls_cap); 802 if (x.events) h->free(h, x.events, sizeof(WSEvent) * x.events_cap); 803 }