From 80e7096f259cc439e130526e1866c50ea30ec701 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 09:08:02 +0900 Subject: [PATCH] cgen: #50 append/insert tagged-dst VALUE boxes PRE-grow, both stages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #34 value-store dispatch's tagged arm ran the widen choke-point (cg_widen_tagged_store / cgwidentaggedstore — which cgexprs the value internally) AFTER cg_append_grow, so a value expression reading the destination (xs.len) saw post-grow state. Hare evaluates arguments before the call's effects; the scalar/str/slice arms already ordered value-first. insert() inherited the bug through its desugar-to-append (9861f73) — one boxing choke-point, so this is the whole fix. Box the value into a fresh per-site @apptagscr frame scratch pre-grow (zero, BP-rooted widen store), then raw-copy the finished box into the new slot post-grow. Consumer evidence: regex fold-5b's {,0} rows (reviewer-5b's mutant; ken's corrected-root matrix /tmp/ken_silent, f50v4_tagged exit 15 both stages, byte-id, gate-blind). lib/regex.ww:643-647's pre-bound split_target workaround comment GRADUATES with this commit; the comment update itself rides the next regex touch per the standing rule. test/807: 57 -> 84 fixtures. New rows: tagged_pregrow_val (f50v4 exact), scalar_pregrow_val (no-regress control), tagged_selfref_val, tagged_str_payload, tagged_regex_minrep (the {,0} shape standalone), tagged_append_pregrow_val (direct append, the fix site), tagged_realloc_selfref_loop (ken k50a: old-base read across actual rt_ensure base moves), tagged_seq_positions (ken k50b: sequenced inserts at 0/mid, each len-reading) and tagged_void_variant (ken k50c: tag-only box through the fresh scratch). Mutation at e8977a4 fails exactly the five eval-order-discriminating tagged rows, both stages. The STRUCT-LITERAL value arm keeps its post-grow field-expr fill — same eval-order class, different arm; filed (ww-core #59), cited at-site, not folded. --- cmd/w6c/cgen.c | 54 +++++- selfhost/cmd/w6c/main.combined.ww | 47 +++++- selfhost/cmd/wcc/cgenexpr.ww | 47 +++++- selfhost/cmd/wwdump/main.combined.ww | 47 +++++- test/wcc/807_insert_elem.c | 235 ++++++++++++++++++++++++++- 5 files changed, 399 insertions(+), 31 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 53f0a8f7..fed46516 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -8012,10 +8012,15 @@ cgexpr(Cg *c, Node *n, Local *locals) } if (el_tagged || el_struct) { /* #34: no register form survives rt_ensure - * for these — grow FIRST, then fill through - * the dst pointer (tagged: the #12 widen - * choke-point cgexprs the value internally; - * struct: literal fill / ident word-copy). */ + * for these. struct: grow FIRST, then fill + * through the dst pointer (literal fill / + * ident word-copy). tagged: #50 — the #12 + * widen choke-point cgexprs the value + * internally, so boxing must run PRE-grow + * (Hare's argument order: a `xs.len` read + * in v sees the pre-append len, like the + * scalar arm); box into a frame scratch, + * grow, raw-copy the finished box in. */ /* #49 (#35's single-element sibling): a * place-chain source (indexed field * `threads[i].root_capture` regex.ha:819, @@ -8122,16 +8127,47 @@ cgexpr(Cg *c, Node *n, Local *locals) } aplace = 1; } + if (el_tagged) { + /* Fresh slot per SITE, not the + * shared per-size scratch: the + * box must stay live across + * rt_ensure, and a nested + * append inside the value + * expression would clobber a + * dedup'd slot (the @apphdrscr + * rationale; #25/#31). */ + int tg_scr = local_alloc(c, + &locals, "@apptagscr", esz, + cg_frame); + ins2(c, A_XORQ, areg(D_AX), + areg(D_AX)); + for (int zk = 0; zk < esz; zk += 8) + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, tg_scr + zk)); + cg_widen_tagged_store(c, &locals, + esub, vn, D_BP, tg_scr, esz); + cg_append_grow(c, sn_direct, sn_off, + sn_scr, esz); + cg_append_slot(c, sn_direct, sn_off, + sn_scr, esz, D_BX); + for (int ck = 0; ck < esz; ck += 8) { + ins2(c, A_MOVQ, + amem(D_BP, tg_scr + ck), + areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BX, ck)); + } + continue; + } cg_append_grow(c, sn_direct, sn_off, sn_scr, esz); cg_append_slot(c, sn_direct, sn_off, sn_scr, esz, D_BX); - if (el_tagged) { - cg_widen_tagged_store(c, &locals, - esub, vn, D_BX, 0, esz); - continue; - } if (vn->kind == N_STRUCTLIT) { + /* #59 (#50's eval-order kin): + * the literal's field exprs + * still eval POST-grow here — + * filed, not folded. */ if (cg_appendscr == 0) cg_appendscr = local_alloc(c, &locals, "@appendscr", 8, diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 1bb85c5a..0b07fcf2 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -26243,10 +26243,14 @@ fn cgappend(c: *cgen, n: *node) void = { continue; }; if (eltagged || elstruct) { - // #34: no register form survives rt_ensure for these — - // grow FIRST, then fill through the dst pointer - // (tagged: the #12 widen choke-point cgexprs the value - // internally; struct: literal fill / ident word-copy). + // #34: no register form survives rt_ensure for these. + // struct: grow FIRST, then fill through the dst pointer + // (literal fill / ident word-copy). tagged: #50 — the + // #12 widen choke-point cgexprs the value internally, + // so boxing must run PRE-grow (Hare's argument order: + // a `xs.len` read in v sees the pre-append len, like + // the scalar arm); box into a frame scratch, grow, + // raw-copy the finished box in. // #49 (#35's single-element sibling): a place-chain // source (indexed field `threads[i].root_capture` // regex.ha:819, deref spine, computed index) SPLITS @@ -26372,14 +26376,43 @@ fn cgappend(c: *cgen, n: *node) void = { }; aplace = true; }; - cgappendgrow(c, sndirect, sn_off, snscr, esz); - cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (eltagged) { - cgwidentaggedstore(c, esubti, vn, "BX", 0, esz); + // Fresh slot per SITE, not the shared per-size + // scratch: the box must stay live across + // rt_ensure, and a nested append inside the + // value expression would clobber a dedup'd + // slot (the @apphdrscr rationale; #25/#31). + let tgscr: i32 = localalloc(c, "@apptagscr", esz, nil); + emitline("\tXORQ\tAX, AX\n"); + let zk: i32 = 0; + for (zk < esz) { + emitline("\tMOVQ\tAX, "); + emitoff((tgscr + zk): i64); + emitline("(BP)\n"); + zk += 8; + }; + cgwidentaggedstore(c, esubti, vn, "BP", tgscr, esz); + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); + let ck: i32 = 0; + for (ck < esz) { + emitline("\tMOVQ\t"); + emitoff((tgscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 8; + }; vn = vn.next; continue; }; + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (vn.kind == nkind.N_STRUCTLIT) { + // #59 (#50's eval-order kin): the literal's field + // exprs still eval POST-grow here — filed, not + // folded. let scroff: i32 = localadd(c, "@appendscr", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(scroff: i64); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 838341c5..31a1842c 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -5399,10 +5399,14 @@ fn cgappend(c: *cgen, n: *node) void = { continue; }; if (eltagged || elstruct) { - // #34: no register form survives rt_ensure for these — - // grow FIRST, then fill through the dst pointer - // (tagged: the #12 widen choke-point cgexprs the value - // internally; struct: literal fill / ident word-copy). + // #34: no register form survives rt_ensure for these. + // struct: grow FIRST, then fill through the dst pointer + // (literal fill / ident word-copy). tagged: #50 — the + // #12 widen choke-point cgexprs the value internally, + // so boxing must run PRE-grow (Hare's argument order: + // a `xs.len` read in v sees the pre-append len, like + // the scalar arm); box into a frame scratch, grow, + // raw-copy the finished box in. // #49 (#35's single-element sibling): a place-chain // source (indexed field `threads[i].root_capture` // regex.ha:819, deref spine, computed index) SPLITS @@ -5528,14 +5532,43 @@ fn cgappend(c: *cgen, n: *node) void = { }; aplace = true; }; - cgappendgrow(c, sndirect, sn_off, snscr, esz); - cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (eltagged) { - cgwidentaggedstore(c, esubti, vn, "BX", 0, esz); + // Fresh slot per SITE, not the shared per-size + // scratch: the box must stay live across + // rt_ensure, and a nested append inside the + // value expression would clobber a dedup'd + // slot (the @apphdrscr rationale; #25/#31). + let tgscr: i32 = localalloc(c, "@apptagscr", esz, nil); + emitline("\tXORQ\tAX, AX\n"); + let zk: i32 = 0; + for (zk < esz) { + emitline("\tMOVQ\tAX, "); + emitoff((tgscr + zk): i64); + emitline("(BP)\n"); + zk += 8; + }; + cgwidentaggedstore(c, esubti, vn, "BP", tgscr, esz); + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); + let ck: i32 = 0; + for (ck < esz) { + emitline("\tMOVQ\t"); + emitoff((tgscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 8; + }; vn = vn.next; continue; }; + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (vn.kind == nkind.N_STRUCTLIT) { + // #59 (#50's eval-order kin): the literal's field + // exprs still eval POST-grow here — filed, not + // folded. let scroff: i32 = localadd(c, "@appendscr", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(scroff: i64); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 00271e58..17f54be9 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -26243,10 +26243,14 @@ fn cgappend(c: *cgen, n: *node) void = { continue; }; if (eltagged || elstruct) { - // #34: no register form survives rt_ensure for these — - // grow FIRST, then fill through the dst pointer - // (tagged: the #12 widen choke-point cgexprs the value - // internally; struct: literal fill / ident word-copy). + // #34: no register form survives rt_ensure for these. + // struct: grow FIRST, then fill through the dst pointer + // (literal fill / ident word-copy). tagged: #50 — the + // #12 widen choke-point cgexprs the value internally, + // so boxing must run PRE-grow (Hare's argument order: + // a `xs.len` read in v sees the pre-append len, like + // the scalar arm); box into a frame scratch, grow, + // raw-copy the finished box in. // #49 (#35's single-element sibling): a place-chain // source (indexed field `threads[i].root_capture` // regex.ha:819, deref spine, computed index) SPLITS @@ -26372,14 +26376,43 @@ fn cgappend(c: *cgen, n: *node) void = { }; aplace = true; }; - cgappendgrow(c, sndirect, sn_off, snscr, esz); - cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (eltagged) { - cgwidentaggedstore(c, esubti, vn, "BX", 0, esz); + // Fresh slot per SITE, not the shared per-size + // scratch: the box must stay live across + // rt_ensure, and a nested append inside the + // value expression would clobber a dedup'd + // slot (the @apphdrscr rationale; #25/#31). + let tgscr: i32 = localalloc(c, "@apptagscr", esz, nil); + emitline("\tXORQ\tAX, AX\n"); + let zk: i32 = 0; + for (zk < esz) { + emitline("\tMOVQ\tAX, "); + emitoff((tgscr + zk): i64); + emitline("(BP)\n"); + zk += 8; + }; + cgwidentaggedstore(c, esubti, vn, "BP", tgscr, esz); + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); + let ck: i32 = 0; + for (ck < esz) { + emitline("\tMOVQ\t"); + emitoff((tgscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 8; + }; vn = vn.next; continue; }; + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (vn.kind == nkind.N_STRUCTLIT) { + // #59 (#50's eval-order kin): the literal's field + // exprs still eval POST-grow here — filed, not + // folded. let scroff: i32 = localadd(c, "@appendscr", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(scroff: i64); diff --git a/test/wcc/807_insert_elem.c b/test/wcc/807_insert_elem.c index d6c70a6f..92dc4801 100644 --- a/test/wcc/807_insert_elem.c +++ b/test/wcc/807_insert_elem.c @@ -56,6 +56,28 @@ * insert_in_loop | front-insert 1,2,3,4 -> [4,3,2,1] — | 47 * | len bookkeeping under iteration + | * | per-position checks | + * tagged_pregrow_val | #50 value eval-order pin, tagged | 50 + * | dst: v = (xs.len: size)+2 reads the | + * | PRE-grow len (post-grow boxing read 5) | + * scalar_pregrow_val | #50 scalar control: value-first | 55 + * | order was already Hare-correct | + * tagged_selfref_val | #50: v = xs[1], an element of the | 51 + * | dst — pre-grow boxing reads OLD base | + * tagged_str_payload | #50: str-variant box runs pre-grow | 52 + * tagged_regex_minrep | #50: the regex {,0} fold-5b shape, | 53 + * | len-reading value cast to a named | + * | variant alias (regex.ww:643-650) | + * tagged_realloc_selfref_loop | #50 ken k50a: 30-append | 56 + * | self-ref loop, value reads xs[0] across| + * | actual rt_ensure base moves | + * tagged_append_pregrow_val | #50 direct append() pin (the | 54 + * | fix site; insert inherits via desugar) | + * tagged_seq_positions | #50 ken k50b: sequenced inserts at | 57 + * | 0 then mid, both len-reading values, | + * | full final-order check [200,10,4,20] | + * tagged_void_variant | #50 ken k50c: void box (tag-only) | 58 + * | through @apptagscr, then a len-reading | + * | size insert over the mixed slice | * * Wants stay under 256 (the exit-status byte); the if-ladder rows * return a distinct small failure code per check, so a wrong element @@ -305,7 +327,7 @@ static const struct row rows[] = { /* CAST-rvalue value into a tagged slice — the regex ha:419/441 * shape `insert(insts[term_start_idx], after_idx: inst_split)`; - * append's widen choke-point boxes it post-grow. */ + * append's widen choke-point boxes it PRE-grow (#50). */ { "tagged_cast", "package main;\n" "type inst_lit = rune;\n" @@ -370,6 +392,217 @@ static const struct row rows[] = { "};\n", 47, NULL }, + /* #50 THE value eval-order pin for the TAGGED-dst arm (ken's + * f50v4_tagged shape exact): v = (xs.len: size)+2 widens into + * the box. Pre-grow len=2 -> v=4; the pre-#50 post-grow boxing + * read len=3 -> v=5 (the exit-15 path). The scalar arm always + * ordered value-first (scalar_pregrow_val below); #50 aligns + * the boxing arm to it. */ + { "tagged_pregrow_val", + "package main;\n" + "type un = (size | void);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 10: size);\n" + "\tappend(xs, 20: size);\n" + "\tinsert(xs[1], ((xs.len: size) + 2));\n" + "\tmatch (xs[1]) {\n" + "\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n" + "\tcase void => { return 2; };\n" + "\t};\n" + "\tif (len(xs) != 3) { return 3; };\n" + "\treturn 50;\n" + "};\n", + 50, NULL }, + + /* #50 scalar no-regress control (ken's f50_insertorder): the + * scalar-dst arm was Hare-correct pre-#50 — value reads the + * pre-grow len. */ + { "scalar_pregrow_val", + "package main;\n" + "export fn main() i32 = {\n" + "\tlet xs: []size = [];\n" + "\tappend(xs, 10: size);\n" + "\tappend(xs, 20: size);\n" + "\tinsert(xs[1], len(xs): size);\n" + "\tif (xs[1] != 2) { return 1; };\n" + "\tif (len(xs) != 3) { return 2; };\n" + "\treturn 55;\n" + "};\n", + 55, NULL }, + + /* #50 SELF-REFERENCE value: v is an element of the destination + * itself — pre-grow boxing must read the OLD base (and survive + * a rt_ensure realloc via the frame scratch): [10,20] -> + * insert(xs[0], xs[1]) -> [20,10,20]. */ + { "tagged_selfref_val", + "package main;\n" + "type un = (size | void);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 10: size);\n" + "\tappend(xs, 20: size);\n" + "\tinsert(xs[0], xs[1]);\n" + "\tmatch (xs[0]) {\n" + "\tcase let v: size => { if (v != 20) { return 1; }; };\n" + "\tcase void => { return 2; };\n" + "\t};\n" + "\tmatch (xs[1]) {\n" + "\tcase let v: size => { if (v != 10) { return 3; }; };\n" + "\tcase void => { return 4; };\n" + "\t};\n" + "\tmatch (xs[2]) {\n" + "\tcase let v: size => { if (v != 20) { return 5; }; };\n" + "\tcase void => { return 6; };\n" + "\t};\n" + "\tif (len(xs) != 3) { return 7; };\n" + "\treturn 51;\n" + "};\n", + 51, NULL }, + + /* #50 str-payload tagged box: the widen store's str branch + * (AX=ptr BX=len) also runs pre-grow now — header lands + * whole. */ + { "tagged_str_payload", + "package main;\n" + "type su = (str | void);\n" + "export fn main() i32 = {\n" + "\tlet ss: []su = [];\n" + "\tappend(ss, \"aa\");\n" + "\tinsert(ss[0], \"bb\");\n" + "\tmatch (ss[0]) {\n" + "\tcase let s: str => {\n" + "\t\tif (s.len != 2) { return 1; };\n" + "\t\tif (s[0] != 'b') { return 2; };\n" + "\t};\n" + "\tcase void => { return 3; };\n" + "\t};\n" + "\tmatch (ss[1]) {\n" + "\tcase let s: str => { if (s[0] != 'a') { return 4; }; };\n" + "\tcase void => { return 5; };\n" + "\t};\n" + "\treturn 52;\n" + "};\n", + 52, NULL }, + + /* #50 the regex {,0}-class shape standalone (regex.ww:643-650, + * the fold-5b consumer reviewer-5b's mutant killed): a + * len-reading value CAST to a named variant alias — + * `((insts.len: size) + 2): inst_split`. Pre-grow len=2 -> 4. */ + { "tagged_regex_minrep", + "package main;\n" + "type ispl = size;\n" + "type un = (ispl | void);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 7: ispl);\n" + "\tappend(xs, 8: ispl);\n" + "\tinsert(xs[0], ((xs.len: size) + 2): ispl);\n" + "\tmatch (xs[0]) {\n" + "\tcase let v: ispl => { if (v != 4) { return (v: i32) + 10; }; };\n" + "\tcase void => { return 2; };\n" + "\t};\n" + "\tif (len(xs) != 3) { return 3; };\n" + "\treturn 53;\n" + "};\n", + 53, NULL }, + + /* #50 ken's k50a: 30 appends force repeated rt_ensure realloc; + * every value reads xs[0] (as-unwrap + arithmetic) off the + * potentially-moved base — the one shape that pins the OLD-base + * read across an ACTUAL base move, which single-grow rows + * can't. */ + { "tagged_realloc_selfref_loop", + "package main;\n" + "type un = (void | size);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 100: size);\n" + "\tlet i: size = 0;\n" + "\tfor (i < 30) {\n" + "\t\tappend(xs, ((xs[0] as size) + i));\n" + "\t\ti += 1;\n" + "\t};\n" + "\tif (len(xs) != 31) { return 1; };\n" + "\tlet k: size = 1;\n" + "\tfor (k < 31) {\n" + "\t\tmatch (xs[k]) {\n" + "\t\tcase let v: size => { if (v != 100 + (k - 1)) { return 2; }; };\n" + "\t\tcase void => { return 3; };\n" + "\t\t};\n" + "\t\tk += 1;\n" + "\t};\n" + "\treturn 56;\n" + "};\n", + 56, NULL }, + + /* #50 direct append() (no insert desugar) — the fix lives in + * append's tagged arm, so pin it without the rotate. */ + { "tagged_append_pregrow_val", + "package main;\n" + "type un = (size | void);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 10: size);\n" + "\tappend(xs, 20: size);\n" + "\tappend(xs, (xs.len: size) + 2);\n" + "\tmatch (xs[2]) {\n" + "\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n" + "\tcase void => { return 2; };\n" + "\t};\n" + "\tif (len(xs) != 3) { return 3; };\n" + "\treturn 54;\n" + "};\n", + 54, NULL }, + + /* #50 ken's k50b: SEQUENCED inserts at position edges (0, then + * mid), each with a len-reading value — every boxing must see + * its own pre-grow len (2 -> 200, then 3 -> 4), and the second + * insert's rotate must shift the first's result correctly: + * [10,20] -> [200,10,20] -> [200,10,4,20]. */ + { "tagged_seq_positions", + "package main;\n" + "type un = (void | size);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 10: size);\n" + "\tappend(xs, 20: size);\n" + "\tinsert(xs[0], ((xs.len: size) * 100));\n" + "\tmatch (xs[0]) {\n" + "\tcase let v: size => { if (v != 200) { return 1; }; };\n" + "\tcase void => { return 2; };\n" + "\t};\n" + "\tinsert(xs[2], ((xs.len: size) + 1));\n" + "\tmatch (xs[2]) {\n" + "\tcase let v: size => { if (v != 4) { return 3; }; };\n" + "\tcase void => { return 4; };\n" + "\t};\n" + "\tmatch (xs[1]) { case let v: size => { if (v != 10) { return 5; }; }; case void => { return 6; }; };\n" + "\tmatch (xs[3]) { case let v: size => { if (v != 20) { return 7; }; }; case void => { return 8; }; };\n" + "\tif (len(xs) != 4) { return 9; };\n" + "\treturn 57;\n" + "};\n", + 57, NULL }, + + /* #50 ken's k50c: VOID-variant value — a tag-only box through + * the fresh @apptagscr (the zeroed scratch IS the payload), + * then a len-reading size insert over the mixed slice. */ + { "tagged_void_variant", + "package main;\n" + "type un = (void | size);\n" + "export fn main() i32 = {\n" + "\tlet xs: []un = [];\n" + "\tappend(xs, 5: size);\n" + "\tinsert(xs[0], void);\n" + "\tif (!(xs[0] is void)) { return 1; };\n" + "\tmatch (xs[1]) { case let v: size => { if (v != 5) { return 2; }; }; case void => { return 3; }; };\n" + "\tinsert(xs[1], ((xs.len: size)));\n" + "\tmatch (xs[1]) { case let v: size => { if (v != 2) { return 4; }; }; case void => { return 5; }; };\n" + "\tif (len(xs) != 3) { return 6; };\n" + "\treturn 58;\n" + "};\n", + 58, NULL }, + { "reject_array", "package main;\n" "export fn main() i32 = {\n"