w6c+w6c_ww: tagged-union struct-lit payload fills via the canonical fill (#23)
The widen choke-point's struct-payload arm carried its own inline N_STRUCTLIT field loop -- a parallel fill that drifted from cg_structlit_fill/cgstructlitfill: no tagged-field widen arm, so a (void|T)-typed field's raw scalar landed in the field's TAG word (silent truncation past the first tagged field, both stages, byte-id, gate-blind; prober-9 PG5). Delete both loops and delegate to the canonical fill at the payload base: one fill path, one widen path, mutually recursive. Inherits the nested-struct/call/arrlit field arms and closes a latent fsz==2 cs!=ww (old ww loop's fieldstoreop MOVW vs cstage MOVQ). Test 938: 15-row table-driven runtime readback (incl. ellipsis autofill, offset-0 tagged field, (void|str) payload, 3-level widen-fill recursion torture), all 13 bug rows silent-fail at master 6699158; 2 rows skip the byte-id check loudly (pre-existing match-on-tagged-FIELD readback cs!=ww, master-confirmed, separate family).
This commit is contained in:
7
Makefile
7
Makefile
@@ -309,6 +309,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tuple_in_union_run \
|
||||
$(BIN)/test_tuple_slot_layout_run \
|
||||
$(BIN)/test_tagged_tuple_widen_run \
|
||||
$(BIN)/test_tagged_structlit_payload_run \
|
||||
$(BIN)/test_forrange_fieldbase_run \
|
||||
$(BIN)/test_errtype_compare \
|
||||
$(BIN)/test_tuple_elem_slice_len_run \
|
||||
@@ -1471,6 +1472,12 @@ $(BIN)/test_tagged_tuple_widen_run: test/wcc/936_tagged_tuple_widen_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_tagged_structlit_payload_run: test/wcc/938_tagged_structlit_payload_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_forrange_fieldbase_run: test/wcc/937_forrange_fieldbase_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -2606,52 +2606,17 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
amem(D_BP, write_off + 8 + k));
|
||||
}
|
||||
} else if (src->kind == N_STRUCTLIT) {
|
||||
for (Node *f = src->list; f; f = f->next) {
|
||||
u64 foff = 0;
|
||||
int fsz = 8;
|
||||
Type *ftype = NULL;
|
||||
for (Tfield *fl = su->fields; fl; fl = fl->next) {
|
||||
if (strcmp(fl->name, f->str) == 0) {
|
||||
foff = fl->offset;
|
||||
fsz = (int)(fl->type ? fl->type->size : 8);
|
||||
ftype = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
int sl_isf32 = 0;
|
||||
if (fld_isfloat(ftype, &sl_isf32)) {
|
||||
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BP, write_off + 8 + (int)foff));
|
||||
continue;
|
||||
}
|
||||
Type *fu = (ftype && ftype->kind == TY_NAMED)
|
||||
? ftype->under : ftype;
|
||||
/* str IS []u8 and a slice is the same 3-word
|
||||
* {ptr,len,cap} header from cgexpr's AX/BX/CX
|
||||
* (#1/Phase 3). The slice arm rides #38b's
|
||||
* regex-shaped consumer (slice fields inside a
|
||||
* union-payload struct literal); the prior
|
||||
* str-only gate dropped .len/.cap via the
|
||||
* scalar store below — the #24 gap's widener
|
||||
* twin. */
|
||||
if (fu && (fu->kind == TY_STR
|
||||
|| fu->kind == TY_SLICE)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + 8 + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, write_off + 8 + (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, write_off + 8 + (int)foff + 16));
|
||||
continue;
|
||||
}
|
||||
int op = A_MOVQ;
|
||||
if (fsz == 1) op = A_MOVB;
|
||||
else if (fsz == 4) op = A_MOVL;
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, write_off + 8 + (int)foff));
|
||||
}
|
||||
/* #23: delegate to the single fill path. The inline
|
||||
* field loop this replaces was a parallel fill that
|
||||
* drifted: it lacked the tagged-field widen arm, so
|
||||
* a (void|T)-typed field's raw scalar landed in the
|
||||
* field's TAG word (silent truncation past the first
|
||||
* tagged field, both stages). Delegation also
|
||||
* inherits the nested-struct / call / array-lit
|
||||
* field arms; float / str / slice / scalar fields
|
||||
* emit byte-identically to the old loop. */
|
||||
cg_structlit_fill(c, locals_p, su, src,
|
||||
DST_BP, 0, NULL, write_off + 8);
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, write_off + 0));
|
||||
|
||||
@@ -19565,58 +19565,24 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let tag: i32 = taggedvariantindext(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
if (src.kind == nkind.N_STRUCTLIT) {
|
||||
let fnode: *node = src.list;
|
||||
for (fnode != nil) {
|
||||
if (fnode.kind == nkind.N_FIELD) {
|
||||
let fname: str = fnode.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fname)) {
|
||||
cgexpr(c, fnode.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isstrtype(c, fi.tnode)
|
||||
|| isslicetype(c, fi.tnode)) {
|
||||
// str IS []u8 and a slice is the
|
||||
// same 3-word {ptr,len,cap} header
|
||||
// from cgexpr's AX/BX/CX (#1/Phase
|
||||
// 3). Slice arm rides #38b's regex-
|
||||
// shaped consumer — the prior str-
|
||||
// only gate dropped .len/.cap (#24
|
||||
// gap's widener twin).
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
}; };
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fnode = fnode.next;
|
||||
};
|
||||
// #23: delegate to the single fill path. The
|
||||
// inline field loop this replaces was a
|
||||
// parallel fill that drifted: it lacked the
|
||||
// tagged-field widen arm, so a (void|T)-typed
|
||||
// field's raw scalar landed in the field's
|
||||
// TAG word (silent truncation past the first
|
||||
// tagged field, both stages). Delegation also
|
||||
// inherits the nested-struct / call / array-
|
||||
// lit field arms; float / str / slice /
|
||||
// scalar fields emit byte-identically to the
|
||||
// old loop EXCEPT fsz==2 scalars, where the
|
||||
// old loop's fieldstoreop emitted MOVW
|
||||
// against cstage's MOVQ — a latent cs≠ww the
|
||||
// fill's #13-pinned dispatch closes. Mirror
|
||||
// of cstage cg_widen_tagged_store's
|
||||
// N_STRUCTLIT arm.
|
||||
cgstructlitfill(c, si, src, 0, 0, "",
|
||||
slot_off + 8);
|
||||
} else {
|
||||
// Struct ident source: byte-copy struct words to slot+8+k.
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
|
||||
@@ -3573,58 +3573,24 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let tag: i32 = taggedvariantindext(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
if (src.kind == nkind.N_STRUCTLIT) {
|
||||
let fnode: *node = src.list;
|
||||
for (fnode != nil) {
|
||||
if (fnode.kind == nkind.N_FIELD) {
|
||||
let fname: str = fnode.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fname)) {
|
||||
cgexpr(c, fnode.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isstrtype(c, fi.tnode)
|
||||
|| isslicetype(c, fi.tnode)) {
|
||||
// str IS []u8 and a slice is the
|
||||
// same 3-word {ptr,len,cap} header
|
||||
// from cgexpr's AX/BX/CX (#1/Phase
|
||||
// 3). Slice arm rides #38b's regex-
|
||||
// shaped consumer — the prior str-
|
||||
// only gate dropped .len/.cap (#24
|
||||
// gap's widener twin).
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
}; };
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fnode = fnode.next;
|
||||
};
|
||||
// #23: delegate to the single fill path. The
|
||||
// inline field loop this replaces was a
|
||||
// parallel fill that drifted: it lacked the
|
||||
// tagged-field widen arm, so a (void|T)-typed
|
||||
// field's raw scalar landed in the field's
|
||||
// TAG word (silent truncation past the first
|
||||
// tagged field, both stages). Delegation also
|
||||
// inherits the nested-struct / call / array-
|
||||
// lit field arms; float / str / slice /
|
||||
// scalar fields emit byte-identically to the
|
||||
// old loop EXCEPT fsz==2 scalars, where the
|
||||
// old loop's fieldstoreop emitted MOVW
|
||||
// against cstage's MOVQ — a latent cs≠ww the
|
||||
// fill's #13-pinned dispatch closes. Mirror
|
||||
// of cstage cg_widen_tagged_store's
|
||||
// N_STRUCTLIT arm.
|
||||
cgstructlitfill(c, si, src, 0, 0, "",
|
||||
slot_off + 8);
|
||||
} else {
|
||||
// Struct ident source: byte-copy struct words to slot+8+k.
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
|
||||
@@ -19565,58 +19565,24 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let tag: i32 = taggedvariantindext(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
if (src.kind == nkind.N_STRUCTLIT) {
|
||||
let fnode: *node = src.list;
|
||||
for (fnode != nil) {
|
||||
if (fnode.kind == nkind.N_FIELD) {
|
||||
let fname: str = fnode.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fname)) {
|
||||
cgexpr(c, fnode.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isstrtype(c, fi.tnode)
|
||||
|| isslicetype(c, fi.tnode)) {
|
||||
// str IS []u8 and a slice is the
|
||||
// same 3-word {ptr,len,cap} header
|
||||
// from cgexpr's AX/BX/CX (#1/Phase
|
||||
// 3). Slice arm rides #38b's regex-
|
||||
// shaped consumer — the prior str-
|
||||
// only gate dropped .len/.cap (#24
|
||||
// gap's widener twin).
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
}; };
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fnode = fnode.next;
|
||||
};
|
||||
// #23: delegate to the single fill path. The
|
||||
// inline field loop this replaces was a
|
||||
// parallel fill that drifted: it lacked the
|
||||
// tagged-field widen arm, so a (void|T)-typed
|
||||
// field's raw scalar landed in the field's
|
||||
// TAG word (silent truncation past the first
|
||||
// tagged field, both stages). Delegation also
|
||||
// inherits the nested-struct / call / array-
|
||||
// lit field arms; float / str / slice /
|
||||
// scalar fields emit byte-identically to the
|
||||
// old loop EXCEPT fsz==2 scalars, where the
|
||||
// old loop's fieldstoreop emitted MOVW
|
||||
// against cstage's MOVQ — a latent cs≠ww the
|
||||
// fill's #13-pinned dispatch closes. Mirror
|
||||
// of cstage cg_widen_tagged_store's
|
||||
// N_STRUCTLIT arm.
|
||||
cgstructlitfill(c, si, src, 0, 0, "",
|
||||
slot_off + 8);
|
||||
} else {
|
||||
// Struct ident source: byte-copy struct words to slot+8+k.
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
|
||||
557
test/wcc/938_tagged_structlit_payload_run.c
Normal file
557
test/wcc/938_tagged_structlit_payload_run.c
Normal file
@@ -0,0 +1,557 @@
|
||||
/*
|
||||
* 938_tagged_structlit_payload_run — #23 (fold-5b prereq): tagged-union
|
||||
* CONSTRUCTION from an INLINE struct-literal payload whose fields are
|
||||
* themselves tagged ((void|size) — the regex inst_repeat shape,
|
||||
* ref/hare/regex/regex.ha:55-60). Pre-#23 the widen choke-point's
|
||||
* struct-payload arm (cg_widen_tagged_store / cgwidentaggedstorebp)
|
||||
* carried its OWN inline N_STRUCTLIT field loop — a parallel fill that
|
||||
* drifted from cg_structlit_fill / cgstructlitfill: it had float/str/
|
||||
* slice/scalar arms but NO tagged-field widen arm, so a (void|T)-typed
|
||||
* field's raw scalar landed in the field's TAG word (silent truncation
|
||||
* past the first tagged field; both stages, byte-id — gate-blind;
|
||||
* prober-9 PG5 evidence, re-confirmed at 6699158). The fix deletes the
|
||||
* parallel loop and delegates to the canonical fill, which also
|
||||
* inherits the nested-struct field arm.
|
||||
*
|
||||
* row | shape | want
|
||||
* ---------------------+----------------------------------------+-----
|
||||
* inst_repeat_shape | rep{id,origin,min=void,max=5:size} — |
|
||||
* | the PG5b repro; 48B payload | 0
|
||||
* rep3_allsize | {id,min,max} all set via `: size` |
|
||||
* | casts — PG5g; tag+payload word checks | 0
|
||||
* tagged_only16 | single (void|size) field — 16B payload | 0
|
||||
* prebound_tagged_field| min = mn (pre-bound (void|size) LOCAL |
|
||||
* | — tagged→tagged subset inside the fill)| 0
|
||||
* str_and_tagged | {s:str, m:(void|size)} — 3-word str |
|
||||
* | arm + tagged arm in one literal | 0
|
||||
* variant_pos0 | union (rep|lit) — struct variant tag 0 | 0
|
||||
* slice_elem_cursorfit | 24B payload through append + indexed |
|
||||
* | match (union 32B = exactly the 4-word |
|
||||
* | tagged cursor; >32B indexed reads are |
|
||||
* | the open over-cap cursor residual, |
|
||||
* | task #37 — NOT pinned here) | 0
|
||||
* nested_struct_field | nested struct-typed field literal in a |
|
||||
* | union payload — delegation-inherited |
|
||||
* | recursion arm (pre-#23: AX-only store) | 0
|
||||
* ellipsis_autofill | `...` autofill + tagged field — the |
|
||||
* | fill's TK_ELLIPSIS zero-fill runs at |
|
||||
* | the widened slot's payload base | 0
|
||||
* two_tagged_offset0 | tagged field at struct OFFSET 0, a 2nd |
|
||||
* | tagged field, then a trailing scalar | 0
|
||||
* nested_and_tagged | nested struct field AND tagged field |
|
||||
* | in ONE literal — fill→widen→fill, both |
|
||||
* | recursion directions in one shape | 0
|
||||
* voidstr_field | (void|str) tagged field — 3-word str |
|
||||
* | payload through the fill's widen arm; |
|
||||
* | pre-#23 the garbage tag matched NO |
|
||||
* | variant (fallthrough-proof readback) | 0
|
||||
* recursion_torture | 3-level tagged(struct{tagged(struct{ |
|
||||
* | tagged(leaf-lit)})}) — the widen↔fill |
|
||||
* | mutual recursion terminates + correct | 0
|
||||
* payload8_boundary | struct payload exactly 8B (single size |
|
||||
* | field) — smallest-payload guard | 0
|
||||
* scalar_neighbor | scalar+f64+str payload, NO tagged |
|
||||
* | field — emission no-regress guard | 0
|
||||
*
|
||||
* Field readback goes through MATCH BINDINGS only: direct field reads
|
||||
* on a struct-w/-tagged-field LOCAL are the open #24 (wwstage
|
||||
* field(SB) fallback), and tagged-field inits use explicit `: size`
|
||||
* casts: the BARE untyped-int form is the open #33 wwstage mis-tag.
|
||||
* Both are filed separately and out of #23's arm. A tagged field
|
||||
* inside a NESTED plain-struct field is the open #38 family (fails
|
||||
* identically for plain locals — pre-existing in the canonical fill/
|
||||
* read path, NOT a #23 regression; probed both ways at 6699158) and
|
||||
* is deliberately NOT a row here.
|
||||
*
|
||||
* Every K_RUN row also asserts cstage/wwstage asm byte-id. NNN<950,
|
||||
* self-contained (/tmp, no imports) — rule-14's selfhost-sibling race
|
||||
* does not apply (936/941 precedent).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp_eq(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb");
|
||||
FILE *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||
int rc = 0;
|
||||
for (;;) {
|
||||
int ca = fgetc(fa), cb = fgetc(fb);
|
||||
if (ca != cb) { rc = -1; break; }
|
||||
if (ca == EOF) break;
|
||||
}
|
||||
fclose(fa); fclose(fb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* noasm: skip the cs/ww byte-id check for rows whose READBACK needs an
|
||||
* inner `match` on a tagged FIELD scrutinee — wwstage routes that load
|
||||
* through a 4-word cursor scratch where cstage copies field words
|
||||
* directly (PRE-EXISTING divergence, confirmed at master 6699158 on
|
||||
* the same sources; construction asm is byte-id up to the readback —
|
||||
* the regex-arc match-on-tagged-struct-field family, needs a filed
|
||||
* task). The skip is printed loudly so it can't rot silently. */
|
||||
struct row { const char *label; const char *src; int want; int noasm; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* the PG5b repro verbatim: only id/origin survived pre-#23
|
||||
* (min's tag word read 0 = void by accident, max's read 5). */
|
||||
{ "inst_repeat_shape",
|
||||
"package main;\n"
|
||||
"type inst_lit = rune;\n"
|
||||
"type rep = struct {\n"
|
||||
" id: size,\n"
|
||||
" origin: size,\n"
|
||||
" min: (void | size),\n"
|
||||
" max: (void | size),\n"
|
||||
"};\n"
|
||||
"type inst = (inst_lit | rep);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: inst = rep { id = 1: size, origin = 2: size, min = void, max = 5: size };\n"
|
||||
" match (v) {\n"
|
||||
" case let ir: rep => {\n"
|
||||
" if (ir.id != 1) { return 1; };\n"
|
||||
" if (ir.origin != 2) { return 2; };\n"
|
||||
" if (!(ir.min is void)) { return 3; };\n"
|
||||
" if (!(ir.max is size)) { return 4; };\n"
|
||||
" if (ir.max as size != 5) { return 5; };\n"
|
||||
" };\n"
|
||||
" case let l: inst_lit => { return 6; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* PG5g: every tagged field set — pre-#23 the raw 2/5 landed in
|
||||
* the tag words and both is-tests failed. */
|
||||
{ "rep3_allsize",
|
||||
"package main;\n"
|
||||
"type inst_lit = rune;\n"
|
||||
"type rep3 = struct { id: size, min: (void | size), max: (void | size) };\n"
|
||||
"type inst = (inst_lit | rep3);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: inst = rep3 { id = 1: size, min = 2: size, max = 5: size };\n"
|
||||
" match (v) {\n"
|
||||
" case let ir: rep3 => {\n"
|
||||
" if (ir.id != 1) { return 1; };\n"
|
||||
" if (!(ir.min is size)) { return 2; };\n"
|
||||
" if (ir.min as size != 2) { return 3; };\n"
|
||||
" if (!(ir.max is size)) { return 4; };\n"
|
||||
" if (ir.max as size != 5) { return 5; };\n"
|
||||
" };\n"
|
||||
" case let l: inst_lit => { return 6; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* minimal payload: the tagged field IS the whole struct. */
|
||||
{ "tagged_only16",
|
||||
"package main;\n"
|
||||
"type only = struct { m: (void | size) };\n"
|
||||
"type u = (rune | only);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: u = only { m = 7: size };\n"
|
||||
" match (v) {\n"
|
||||
" case let o: only => {\n"
|
||||
" if (!(o.m is size)) { return 1; };\n"
|
||||
" if (o.m as size != 7) { return 2; };\n"
|
||||
" };\n"
|
||||
" case => return 3;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* field value is a pre-bound tagged LOCAL — the fill's widen arm
|
||||
* takes the tagged→tagged identity-copy route. */
|
||||
{ "prebound_tagged_field",
|
||||
"package main;\n"
|
||||
"type inst_lit = rune;\n"
|
||||
"type rep3 = struct { id: size, min: (void | size), max: (void | size) };\n"
|
||||
"type inst = (inst_lit | rep3);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let mn: (void | size) = 2: size;\n"
|
||||
" let v: inst = rep3 { id = 3: size, min = mn, max = void };\n"
|
||||
" match (v) {\n"
|
||||
" case let ir: rep3 => {\n"
|
||||
" if (ir.id != 3) { return 1; };\n"
|
||||
" if (!(ir.min is size)) { return 2; };\n"
|
||||
" if (ir.min as size != 2) { return 3; };\n"
|
||||
" if (!(ir.max is void)) { return 4; };\n"
|
||||
" };\n"
|
||||
" case let l: inst_lit => { return 5; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* str field (3-word arm) and tagged field in the same literal —
|
||||
* the arms must not disturb each other's offsets. */
|
||||
{ "str_and_tagged",
|
||||
"package main;\n"
|
||||
"type inst_lit = rune;\n"
|
||||
"type named = struct { s: str, m: (void | size) };\n"
|
||||
"type inst = (inst_lit | named);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: inst = named { s = \"hi\", m = 9: size };\n"
|
||||
" match (v) {\n"
|
||||
" case let nm: named => {\n"
|
||||
" if (len(nm.s) != 2) { return 1; };\n"
|
||||
" if (!(nm.m is size)) { return 2; };\n"
|
||||
" if (nm.m as size != 9) { return 3; };\n"
|
||||
" };\n"
|
||||
" case let l: inst_lit => { return 4; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* struct variant FIRST in the union — tag 0; pins that the fix
|
||||
* is position-independent. */
|
||||
{ "variant_pos0",
|
||||
"package main;\n"
|
||||
"type inst_lit = rune;\n"
|
||||
"type rep3 = struct { id: size, min: (void | size), max: (void | size) };\n"
|
||||
"type inst = (rep3 | inst_lit);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: inst = rep3 { id = 1: size, min = void, max = 8: size };\n"
|
||||
" match (v) {\n"
|
||||
" case let ir: rep3 => {\n"
|
||||
" if (ir.id != 1) { return 1; };\n"
|
||||
" if (!(ir.min is void)) { return 2; };\n"
|
||||
" if (!(ir.max is size)) { return 3; };\n"
|
||||
" if (ir.max as size != 8) { return 4; };\n"
|
||||
" };\n"
|
||||
" case let l: inst_lit => { return 5; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* construction → append → indexed match, end-to-end. Payload 24B
|
||||
* keeps the union at 32B — exactly the 4-word tagged cursor, so
|
||||
* the indexed read is in-cap. The >32B indexed read truncates at
|
||||
* the cursor (open over-cap residual, #22b/#36 family) — the
|
||||
* 48B-payload rows above therefore match the LOCAL only. */
|
||||
{ "slice_elem_cursorfit",
|
||||
"package main;\n"
|
||||
"type inst_lit = rune;\n"
|
||||
"type rep1 = struct { id: size, m: (void | size) };\n"
|
||||
"type inst = (inst_lit | rep1);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let insts: []inst = [];\n"
|
||||
" append(insts, ('a': inst_lit));\n"
|
||||
" let v: inst = rep1 { id = 4: size, m = 6: size };\n"
|
||||
" append(insts, v);\n"
|
||||
" if (len(insts) != 2) { return 1; };\n"
|
||||
" match (insts[1]) {\n"
|
||||
" case let r: rep1 => {\n"
|
||||
" if (r.id != 4) { return 2; };\n"
|
||||
" if (!(r.m is size)) { return 3; };\n"
|
||||
" if (r.m as size != 6) { return 4; };\n"
|
||||
" };\n"
|
||||
" case let l: inst_lit => { return 5; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* nested struct-typed field literal inside the union payload —
|
||||
* rides the canonical fill's recursion arm the delegation
|
||||
* inherits (pre-#23 the parallel loop stored AX only). */
|
||||
{ "nested_struct_field",
|
||||
"package main;\n"
|
||||
"type inner = struct { a: size, b: size };\n"
|
||||
"type outer = struct { id: size, in_: inner };\n"
|
||||
"type u = (rune | outer);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: u = outer { id = 1: size, in_ = inner { a = 2: size, b = 3: size } };\n"
|
||||
" match (v) {\n"
|
||||
" case let o: outer => {\n"
|
||||
" if (o.id != 1) { return 1; };\n"
|
||||
" if (o.in_.a != 2) { return 2; };\n"
|
||||
" if (o.in_.b != 3) { return 3; };\n"
|
||||
" };\n"
|
||||
" case => return 4;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* `...` autofill + tagged field (ken n7): the fill's TK_ELLIPSIS
|
||||
* zero-fill loop must run at the widened slot's payload base, not
|
||||
* the slot base — pre-#23 exit 3 (raw 5 in m's tag word). */
|
||||
{ "ellipsis_autofill",
|
||||
"package main;\n"
|
||||
"type st = struct { a: u64, m: (void | size) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let e: (void | st) = st { m = 5: size, ... };\n"
|
||||
" match (e) {\n"
|
||||
" case let s: st => {\n"
|
||||
" if (s.a != 0u64) { return 2; };\n"
|
||||
" if (!(s.m is size)) { return 3; };\n"
|
||||
" if (s.m as size != 5) { return 4; };\n"
|
||||
" };\n"
|
||||
" case void => { return 9; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* tagged field at struct OFFSET 0 plus a second tagged field and
|
||||
* a TRAILING scalar (ken n2b) — pins offset-0 tag placement and
|
||||
* the scalar offset computed past two 16B tagged fields. */
|
||||
{ "two_tagged_offset0",
|
||||
"package main;\n"
|
||||
"type d2 = struct { m: (void | size), n: (void | i64), z: u64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let e: (void | d2) = d2 { m = 4: size, n = (-9): i64, z = 77u64 };\n"
|
||||
" match (e) {\n"
|
||||
" case let s: d2 => {\n"
|
||||
" if (!(s.m is size)) { return 2; };\n"
|
||||
" if (s.m as size != 4) { return 3; };\n"
|
||||
" if (!(s.n is i64)) { return 4; };\n"
|
||||
" if (s.n as i64 != -9) { return 5; };\n"
|
||||
" if (s.z != 77u64) { return 6; };\n"
|
||||
" };\n"
|
||||
" case void => { return 9; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* nested struct field AND tagged field in the SAME literal (ken
|
||||
* n5b) — exercises fill→fill (nested) and fill→widen (tagged)
|
||||
* from one delegated call. */
|
||||
{ "nested_and_tagged",
|
||||
"package main;\n"
|
||||
"type inn = struct { v: u64 };\n"
|
||||
"type outt = struct { i: inn, m: (void | size) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let e: (void | outt) = outt { i = inn { v = 11u64 }, m = 8: size };\n"
|
||||
" match (e) {\n"
|
||||
" case let s: outt => {\n"
|
||||
" if (s.i.v != 11u64) { return 2; };\n"
|
||||
" if (!(s.m is size)) { return 3; };\n"
|
||||
" if (s.m as size != 8) { return 4; };\n"
|
||||
" };\n"
|
||||
" case void => { return 9; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* (void|str) tagged field — 3-word str payload through the fill's
|
||||
* widen arm. Readback initialises `got = 9` BEFORE the inner
|
||||
* match so a garbage tag matching NO variant can't fall through
|
||||
* to a fake 0 (pre-#23 the raw str ptr landed in the tag word
|
||||
* and exactly that fallthrough faked a pass). */
|
||||
{ "voidstr_field",
|
||||
"package main;\n"
|
||||
"type st = struct { id: size, s: (void | str) };\n"
|
||||
"type u = (rune | st);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: u = st { id = 3: size, s = \"hello\" };\n"
|
||||
" match (v) {\n"
|
||||
" case let o: st => {\n"
|
||||
" if (o.id != 3) { return 1; };\n"
|
||||
" let got: i32 = 9;\n"
|
||||
" match (o.s) {\n"
|
||||
" case let sv: str => {\n"
|
||||
" if (len(sv) == 5) { got = 0; } else { got = 2; };\n"
|
||||
" };\n"
|
||||
" case void => { got = 3; };\n"
|
||||
" };\n"
|
||||
" return got;\n"
|
||||
" };\n"
|
||||
" case => return 4;\n"
|
||||
" };\n"
|
||||
" return 5;\n"
|
||||
"};\n", 0, 1 },
|
||||
/* 3-level widen↔fill mutual recursion: tagged(struct{tagged(
|
||||
* struct{tagged(struct-lit)})}) — termination AND correctness of
|
||||
* the #23 delegation cycle (pre-#23 exit 4: mid's tag dropped). */
|
||||
{ "recursion_torture",
|
||||
"package main;\n"
|
||||
"type leaf = struct { p: size };\n"
|
||||
"type t1 = (void | leaf);\n"
|
||||
"type mid = struct { t: t1 };\n"
|
||||
"type t2 = (void | mid);\n"
|
||||
"type outr = struct { id: size, t: t2 };\n"
|
||||
"type u = (rune | outr);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: u = outr { id = 1: size, t = mid { t = leaf { p = 9: size } } };\n"
|
||||
" match (v) {\n"
|
||||
" case let o: outr => {\n"
|
||||
" if (o.id != 1) { return 1; };\n"
|
||||
" match (o.t) {\n"
|
||||
" case let m: mid => {\n"
|
||||
" match (m.t) {\n"
|
||||
" case let l: leaf => {\n"
|
||||
" if (l.p != 9) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
" };\n"
|
||||
" case void => { return 3; };\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" case void => { return 4; };\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" case => return 5;\n"
|
||||
" };\n"
|
||||
" return 6;\n"
|
||||
"};\n", 0, 1 },
|
||||
/* struct payload exactly 8B — the smallest payload through the
|
||||
* widen→fill delegation (boundary guard; worked pre-#23 too). */
|
||||
{ "payload8_boundary",
|
||||
"package main;\n"
|
||||
"type one = struct { a: size };\n"
|
||||
"type u = (rune | one);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: u = one { a = 42: size };\n"
|
||||
" match (v) {\n"
|
||||
" case let o: one => { if (o.a != 42) { return 1; }; };\n"
|
||||
" case => return 2;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
/* no tagged field at all — the previously-working scalar/float/
|
||||
* str arms must emit unchanged through the delegation. */
|
||||
{ "scalar_neighbor",
|
||||
"package main;\n"
|
||||
"type plain = struct { a: size, f: f64, s: str };\n"
|
||||
"type u = (rune | plain);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: u = plain { a = 11: size, f = 2.5, s = \"xyz\" };\n"
|
||||
" match (v) {\n"
|
||||
" case let p: plain => {\n"
|
||||
" if (p.a != 11) { return 1; };\n"
|
||||
" if (p.f != 2.5) { return 2; };\n"
|
||||
" if (len(p.s) != 3) { return 3; };\n"
|
||||
" };\n"
|
||||
" case => return 4;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], errf[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/tsp_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tsp_%d_d_%d", getpid(), i);
|
||||
snprintf(errf, sizeof errf, "/tmp/tsp_%d_e_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s",
|
||||
tmpdir, driver, src, errf);
|
||||
int brc = runwait(cmd);
|
||||
if (brc != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); unlink(errf); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[256];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); unlink(errf); rmdir(tmpdir);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
|
||||
r->label, driver, got, r->want);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cs==ww .s byte-id (rule 10). */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[96], cs[96], ws[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/tsp_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/tsp_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/tsp_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
int rc = slurp_eq(cs, ws);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[2120], wdrv[2120];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].noasm) {
|
||||
printf("row[%s]: asm byte-id SKIPPED "
|
||||
"(pre-existing match-on-tagged-field "
|
||||
"cs/ww readback divergence, "
|
||||
"master-confirmed)\n", rows[i].label);
|
||||
continue;
|
||||
}
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "tagged_structlit_payload: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_structlit_payload: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user