w6c_ww: matchscrutt resolves non-ident index bases via stamped type (fix #48)
Pre-#48 wwstage matchscrutt's N_INDEX arm required ibase.kind == N_IDENT; an index over any other base (match (h.xs[i]) = N_INDEX over N_DOT, the regex fold-2a re.insts[i] shape) returned nil, so cgmatch dispatched with scrutt=nil — every case arm's variant index clamped to 0 (CMPQ $0) and @match_spill fell to the 16B default. SILENT cs≠ww runtime-wrong (cstage N_MATCH reads the checker-stamped s->type for every scrutinee shape, cmd/w6c/cgen.c:7510). The non-ident-base arm now returns the scrutinee node itself behind an istaggedtype gate — the stamped-carrier pattern of the #67 N_DOT arm and the #45 cgtypetest fix — so any base shape resolves the element's tagged tinfo for both variant indices and spill sizing. Same-class load half, one commit per the #133-expanded precedent: cgindex's generic-fallback tagged-element load was the only arm missing the slot>24 R8 word (both ident arms and cstage cgen.c:9106-9117 have it), so a >24B-slot element via a non-ident base under-read the cursor and the now-correctly-sized spill stored stale R8. 928_match_nonident_idx_run pins the repro shape (field-base slice index, all variants both polarities), the regex shape (56B-slot inst-like union, payload reads within the 32B cursor per #43), and ident/array/slice ident-base controls — per row cs==ww byte-id + runtime via both drivers. w6c/wwdump combined.ww regen'd via canonical make; selfhost corpus hand-cmp'd cs==ww both stages. Pre-existing siblings surfaced while probing, NOT folded (rule 11), reported for filing: (a) cgindex element classification skips N_CALL bases entirely (mk()[0] — wrong esz + not tagged-classified, cs≠ww, runtime-wrong, also non-match contexts); (b) `as` on a non-ident carrier still clamps the variant to 0 (cgtagvariantidx's N_TTAGGED node gate rejects the stamped carrier; byte-identical to master, the #200 spill fix covered only slot sizing).
This commit is contained in:
7
Makefile
7
Makefile
@@ -282,6 +282,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tagged_sret_run \
|
||||
$(BIN)/test_tagscr_sizes_run \
|
||||
$(BIN)/test_is_nonident_run \
|
||||
$(BIN)/test_match_nonident_idx_run \
|
||||
$(BIN)/test_global_sret_run \
|
||||
$(BIN)/test_sret_narrow_field \
|
||||
$(BIN)/test_sret_narrow_field_run \
|
||||
@@ -1185,6 +1186,12 @@ $(BIN)/test_is_nonident_run: test/wcc/927_is_nonident_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_match_nonident_idx_run: test/wcc/928_match_nonident_idx_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_global_sret_run: test/wcc/940_global_sret_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -17790,7 +17790,18 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
if (k == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase == nil) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) {
|
||||
// #48: non-ident index base (s.field[i], call()[i],
|
||||
// nested). Only idents carry a declared tnode to walk,
|
||||
// so resolve from the checker-stamped element tinfo
|
||||
// instead — the #45/#67 stamped-carrier pattern; cgmatch
|
||||
// gates on istaggedtype and reads scrutt.type_ directly.
|
||||
// cstage N_MATCH reads s->type for every scrutinee shape
|
||||
// (cmd/w6c/cgen.c:7510). Pre-#48 this returned nil and
|
||||
// the variant clamped to 0 + @match_spill mis-sized.
|
||||
if (istaggedtype(c, scrut)) { return scrut; };
|
||||
return nil;
|
||||
};
|
||||
let bl: *local = localfindnode(c, ibase.str);
|
||||
let btn: *node = nil;
|
||||
if (bl != nil) { btn = bl.tnode; }
|
||||
@@ -21468,8 +21479,15 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (elem_tagged) {
|
||||
// AX holds the element address. Copy to BX (loading slot+0
|
||||
// into AX clobbers it), then read slot words.
|
||||
// into AX clobbers it), then read slot words. #48: the >24
|
||||
// R8 word was missing ONLY in this fallback arm (both ident
|
||||
// arms have it) — a >24B-slot element via a non-ident base
|
||||
// under-read the cursor and the match spill stored stale R8.
|
||||
// Mirrors cstage cgen.c:9106-9117.
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
if (elem_slot_sz > 24) {
|
||||
emitline("\tMOVQ\t24(BX), R8\n");
|
||||
};
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
|
||||
@@ -1609,8 +1609,15 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (elem_tagged) {
|
||||
// AX holds the element address. Copy to BX (loading slot+0
|
||||
// into AX clobbers it), then read slot words.
|
||||
// into AX clobbers it), then read slot words. #48: the >24
|
||||
// R8 word was missing ONLY in this fallback arm (both ident
|
||||
// arms have it) — a >24B-slot element via a non-ident base
|
||||
// under-read the cursor and the match spill stored stale R8.
|
||||
// Mirrors cstage cgen.c:9106-9117.
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
if (elem_slot_sz > 24) {
|
||||
emitline("\tMOVQ\t24(BX), R8\n");
|
||||
};
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
|
||||
@@ -2098,7 +2098,18 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
if (k == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase == nil) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) {
|
||||
// #48: non-ident index base (s.field[i], call()[i],
|
||||
// nested). Only idents carry a declared tnode to walk,
|
||||
// so resolve from the checker-stamped element tinfo
|
||||
// instead — the #45/#67 stamped-carrier pattern; cgmatch
|
||||
// gates on istaggedtype and reads scrutt.type_ directly.
|
||||
// cstage N_MATCH reads s->type for every scrutinee shape
|
||||
// (cmd/w6c/cgen.c:7510). Pre-#48 this returned nil and
|
||||
// the variant clamped to 0 + @match_spill mis-sized.
|
||||
if (istaggedtype(c, scrut)) { return scrut; };
|
||||
return nil;
|
||||
};
|
||||
let bl: *local = localfindnode(c, ibase.str);
|
||||
let btn: *node = nil;
|
||||
if (bl != nil) { btn = bl.tnode; }
|
||||
|
||||
@@ -17790,7 +17790,18 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
if (k == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase == nil) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) {
|
||||
// #48: non-ident index base (s.field[i], call()[i],
|
||||
// nested). Only idents carry a declared tnode to walk,
|
||||
// so resolve from the checker-stamped element tinfo
|
||||
// instead — the #45/#67 stamped-carrier pattern; cgmatch
|
||||
// gates on istaggedtype and reads scrutt.type_ directly.
|
||||
// cstage N_MATCH reads s->type for every scrutinee shape
|
||||
// (cmd/w6c/cgen.c:7510). Pre-#48 this returned nil and
|
||||
// the variant clamped to 0 + @match_spill mis-sized.
|
||||
if (istaggedtype(c, scrut)) { return scrut; };
|
||||
return nil;
|
||||
};
|
||||
let bl: *local = localfindnode(c, ibase.str);
|
||||
let btn: *node = nil;
|
||||
if (bl != nil) { btn = bl.tnode; }
|
||||
@@ -21468,8 +21479,15 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (elem_tagged) {
|
||||
// AX holds the element address. Copy to BX (loading slot+0
|
||||
// into AX clobbers it), then read slot words.
|
||||
// into AX clobbers it), then read slot words. #48: the >24
|
||||
// R8 word was missing ONLY in this fallback arm (both ident
|
||||
// arms have it) — a >24B-slot element via a non-ident base
|
||||
// under-read the cursor and the match spill stored stale R8.
|
||||
// Mirrors cstage cgen.c:9106-9117.
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
if (elem_slot_sz > 24) {
|
||||
emitline("\tMOVQ\t24(BX), R8\n");
|
||||
};
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
|
||||
268
test/wcc/928_match_nonident_idx_run.c
Normal file
268
test/wcc/928_match_nonident_idx_run.c
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 928_match_nonident_idx_run — `match` on an N_INDEX scrutinee whose
|
||||
* BASE is not an ident (#48).
|
||||
*
|
||||
* Pre-#48 wwstage matchscrutt's N_INDEX arm required ibase.kind ==
|
||||
* N_IDENT; an index over a struct field (`match (h.xs[i])` = N_INDEX
|
||||
* over N_DOT) returned nil → cgmatch dispatched with scrutt = nil:
|
||||
* every case arm's variant index clamped to 0 (CMPQ $0) and the
|
||||
* @match_spill slot fell to the 16B default. SILENT cs≠ww miscompile
|
||||
* (cstage N_MATCH reads the checker-stamped s->type for every
|
||||
* scrutinee shape, cmd/w6c/cgen.c:7510); regex fold-2a's
|
||||
* `match (re.insts[i])` is the consumer that surfaced it. The fix
|
||||
* resolves the element type from the stamped .type_ (the #45/#67
|
||||
* stamped-carrier pattern), plus the load-half twin: cgindex's
|
||||
* generic-fallback tagged-element load was the only arm missing the
|
||||
* `slot > 24` R8 word (cgen.c:9106-9117), so a >24B-slot element via
|
||||
* a non-ident base under-read the cursor.
|
||||
*
|
||||
* Rows pin: the discovering repro shape (field-base slice index, all
|
||||
* three variants matched both ways), the regex shape (56B-slot
|
||||
* inst-like union over r.insts[i]; payload reads stay within the 32B
|
||||
* cursor — words past R8 are #43's deferred residual), and the ident
|
||||
* scrutinee / ident-base array + slice index controls (byte-id at
|
||||
* master per the #48 scoping probes; cs is untouched by the fix, so
|
||||
* the per-row cs==ww cmp pins them unchanged). A call-BASE index
|
||||
* (`mk()[0]`) is NOT covered: cgindex's element classification (esz +
|
||||
* tagged) skips N_CALL bases — pre-existing sibling, filed separately.
|
||||
* Per row: w6c vs w6c_ww byte-id (rule 10 — cstage is the
|
||||
* runtime-correct reference) + runtime via both drivers.
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want;
|
||||
};
|
||||
|
||||
#define TU_TYPES \
|
||||
"type va = void;\n" \
|
||||
"type vb = bool;\n" \
|
||||
"type vc = size;\n" \
|
||||
"type tu = (va | vb | vc);\n"
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* The discovering repro: slice field of a struct, indexed.
|
||||
* Pre-#48 wwstage dispatched the vb element to the FIRST arm
|
||||
* regardless of pattern (tag compared against 0) — exit 1. The
|
||||
* third match pins arm-order independence (variant 1 matched
|
||||
* from a non-first arm). */
|
||||
{ "field_slice_idx",
|
||||
TU_TYPES
|
||||
"type holder = struct { xs: []tu, n: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let sl: []tu = [(true: vb), ((7: size): vc)];\n"
|
||||
" let h: holder;\n"
|
||||
" h.xs = sl;\n"
|
||||
" h.n = 2;\n"
|
||||
" match (h.xs[0]) {\n"
|
||||
" case let b: vb => { if (!(b: bool)) { return 2; }; };\n"
|
||||
" case => return 1;\n"
|
||||
" };\n"
|
||||
" match (h.xs[1]) {\n"
|
||||
" case let s: vc => { if ((s: size) != 7) { return 4; }; };\n"
|
||||
" case => return 3;\n"
|
||||
" };\n"
|
||||
" match (h.xs[0]) {\n"
|
||||
" case va => return 5;\n"
|
||||
" case let b: vb => { if (!(b: bool)) { return 6; }; };\n"
|
||||
" case vc => return 7;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* The regex fold-2a shape: 56B-slot inst-like union (8B tag +
|
||||
* 48B widest payload) matched over a struct-field slice index.
|
||||
* The big variant drives slot/spill sizing past 24B (the R8
|
||||
* word both in cgindex's fallback load and cgmatch's spill);
|
||||
* matched payloads read word 1 only — within the 32B cursor. */
|
||||
{ "regex_inst_56b",
|
||||
"type big = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n"
|
||||
"type ilit = struct { r: i64 };\n"
|
||||
"type imatch = void;\n"
|
||||
"type inst = (big | ilit | imatch);\n"
|
||||
"type re = struct { insts: []inst, n: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let sl: []inst;\n"
|
||||
" let l: ilit = ilit { r = 65 };\n"
|
||||
" let v: inst = l;\n"
|
||||
" append(sl, v);\n"
|
||||
" let m: inst = void: imatch;\n"
|
||||
" append(sl, m);\n"
|
||||
" let r: re;\n"
|
||||
" r.insts = sl;\n"
|
||||
" r.n = 2;\n"
|
||||
" match (r.insts[0]) {\n"
|
||||
" case let x: ilit => { if (x.r != 65) { return 2; }; };\n"
|
||||
" case => return 1;\n"
|
||||
" };\n"
|
||||
" match (r.insts[1]) {\n"
|
||||
" case imatch => { };\n"
|
||||
" case => return 3;\n"
|
||||
" };\n"
|
||||
" match (r.insts[0]) {\n"
|
||||
" case big => return 4;\n"
|
||||
" case let x: ilit => { if (x.r != 65) { return 5; }; };\n"
|
||||
" case imatch => return 6;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Controls: ident scrutinee + ident-base array and slice index
|
||||
* — the pre-existing matchscrutt/cgmatch paths the fix must not
|
||||
* disturb (asm byte-id at master per the scoping probes). */
|
||||
{ "ident_and_identbase_controls",
|
||||
TU_TYPES
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: tu = (true: vb);\n"
|
||||
" match (v) {\n"
|
||||
" case let b: vb => { if (!(b: bool)) { return 2; }; };\n"
|
||||
" case => return 1;\n"
|
||||
" };\n"
|
||||
" let arr: [2]tu = [(true: vb), ((7: size): vc)];\n"
|
||||
" match (arr[1]) {\n"
|
||||
" case let s: vc => { if ((s: size) != 7) { return 4; }; };\n"
|
||||
" case => return 3;\n"
|
||||
" };\n"
|
||||
" let sl: []tu = [(true: vb), ((7: size): vc)];\n"
|
||||
" match (sl[0]) {\n"
|
||||
" case let b: vb => { if (!(b: bool)) { return 6; }; };\n"
|
||||
" case => return 5;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static const char *g_bin;
|
||||
|
||||
static int
|
||||
compile_s(const char *tool, const char *src, const char *outpath)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s/%s %s > %s 2>&1",
|
||||
g_bin, tool, src, outpath);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
file_eq(const char *a, const char *b)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "cmp -s %s %s", a, b);
|
||||
return runwait(cmd) == 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const char *src, const char *label)
|
||||
{
|
||||
char tmpdir[128], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/matchnidx_%d_d", getpid());
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/%s build %s >/dev/null 2>&1",
|
||||
tmpdir, g_bin, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
label, driver);
|
||||
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(outbin);
|
||||
rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
static char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
g_bin = bin;
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
const struct row *r = &rows[i];
|
||||
char src[128], cs_s[128], ww_s[128];
|
||||
snprintf(src, sizeof src, "/tmp/matchnidx_%d_%d.ww",
|
||||
getpid(), i);
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/matchnidx_%d_%d_cs.s",
|
||||
getpid(), i);
|
||||
snprintf(ww_s, sizeof ww_s, "/tmp/matchnidx_%d_%d_ww.s",
|
||||
getpid(), i);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return 1;
|
||||
fputs("package main;\n\n", f);
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
total++;
|
||||
int cs_rc = compile_s("w6c", src, cs_s);
|
||||
int ww_rc = compile_s("w6c_ww", src, ww_s);
|
||||
if (cs_rc != 0 || ww_rc != 0) {
|
||||
fprintf(stderr, "FAIL row[%s]: compile rc cs=%d "
|
||||
"ww=%d\n", r->label, cs_rc, ww_rc);
|
||||
fail++;
|
||||
unlink(src); unlink(cs_s); unlink(ww_s);
|
||||
continue;
|
||||
}
|
||||
if (!file_eq(cs_s, ww_s)) {
|
||||
fprintf(stderr, "FAIL row[%s]: cs != ww .s\n",
|
||||
r->label);
|
||||
fail++;
|
||||
}
|
||||
int got_cs = run_driver("ww", src, r->label);
|
||||
if (got_cs != r->want) {
|
||||
fprintf(stderr, "FAIL row[%s] cstage: want %d "
|
||||
"got %d\n", r->label, r->want, got_cs);
|
||||
fail++;
|
||||
}
|
||||
char wwdrv[600];
|
||||
snprintf(wwdrv, sizeof wwdrv, "%s/ww_ww", g_bin);
|
||||
if (access(wwdrv, X_OK) == 0) {
|
||||
int got_ww = run_driver("ww_ww", src, r->label);
|
||||
if (got_ww != r->want) {
|
||||
fprintf(stderr, "FAIL row[%s] wwstage: "
|
||||
"want %d got %d\n",
|
||||
r->label, r->want, got_ww);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ww_s);
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "match_nonident_idx_run: %d/%d rows failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("match_nonident_idx_run: %d rows ok\n", total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user