cgen: type-key the struct field-layout receiver arms via stamped tinfo (#31 c1)
wwstage cgen resolved struct field LAYOUT (offset + field type) by bare-leaf
name (structlookupchain / structlookup / letvarstructinfo) at nine field
READ / addr-of / let-copy receiver arms whose base is a *struct pointer, a
value struct, or a module global. Under a cross-module same-leaf collision
(two modules each exporting a `pair`, 16B vs 24B) the bare-leaf lookup
first-matches the wrong-size struct -> the field is read / addressed / copied
at the wrong offset with the wrong width. cstage type-keys off the stamped
node.type_ (type_chase_named(base->type)->fields) and is correct; this aligns
wwstage UP to it (ww-only change).
Route the nine arms off the stamped receiver tinfo: R1/R2 *struct reads via a
new shared choke-point cgptrfieldloadtf (the tinfo twin of cgptrfieldload);
A1/A2/A3 addr-of and W3 scalar global-store via tichase(recv.type_)->fields;
C1/W4a copy/size via structabisizetn(tichase(.type_)). Mirrors #21 (5ae6e34);
the *struct arms peel the pointee with tichase(.type_).sub, the faithful twin
of cstage type_chase_named(bu->sub).
The global value-struct arms (R2/R3/A3/W3/W4a) are converted-for-construction:
a global struct's type is always explicitly qualified, so the bare leaf already
resolved correctly and they cannot be reddened -- byte-id (cs.s==ww.s) is their
net, not a value pin. Labelled so the absent reddening pin is explained, not
silent.
Commit 1 of a 2-commit arc (RULING R2 / Opt-2, .ai/ken-31-spec.md): closes the
field-LAYOUT read/copy/addr surface. Commit 2 converts the W1/W2/W5/W4b store
loops to a tinfo-native fill (cgstructlitfilltn + sretretsizetn) and closes the
in-loop nested sub-arms by construction. Part of the #224 name-keyed-cgen
cluster retirement.
Pin: test/wcc/797 value-asserts R1 ptr-read / C1 let-copy / A1/A2 addr, each
reddening under independent per-arm revert (a ratchet proves nothing for this
silent-capable class).
This commit is contained in:
305
test/wcc/797_xmod_struct_field_layout_collide_run.c
Normal file
305
test/wcc/797_xmod_struct_field_layout_collide_run.c
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* 794_xmod_struct_field_layout_collide_run — project #31 (RULING R2,
|
||||
* commit-1: the OFFSET/SIZE align-up arms) runtime + byte-id net. The
|
||||
* direct continuation of 793 (#21): #21 type-keyed the THREE caller-side
|
||||
* sites the 793 collision drove (push/recv/field-read); #31 closes the
|
||||
* field-LAYOUT *receiver* arms #21 left name-keyed. Commit-1 covers the
|
||||
* offset/size half:
|
||||
*
|
||||
* - R1 (cgenexpr cgdot, local *struct READ): `p.f` where p is an
|
||||
* inferred-let *struct resolved the field OFFSET via
|
||||
* structlookupchain(inner) → the FOREIGN same-leaf struct's layout
|
||||
* (wrong offset + wrong load-op). Now tichase(dotlhs.type_)->.sub
|
||||
* ->fields (the #21 R0 tfield template), via the shared read
|
||||
* choke-point cgptrfieldloadtf.
|
||||
* - C1 (cgenstmt cgletbody, let-COPY size): `let p2 = p1` sized the
|
||||
* struct memcpy run via structlookup(tn.str) → the foreign 24B
|
||||
* struct, OVER-reading a 16B source + OVER-writing p2's slot (a
|
||||
* SILENT OOB whose *value* coincidentally round-trips — the asm
|
||||
* diverges, cs!=ww). Now structabisizetn(rhs.type_) (the COPY
|
||||
* source's stamped tinfo).
|
||||
* - A1/A2 (cgenexpr cgun, addr-of `&p.f` / `&o.f`): the LEAQ field
|
||||
* offset came from structlookupchain → wrong offset. Now
|
||||
* tichase(opnd.lhs.type_) (->.sub for the *struct A1 arm).
|
||||
*
|
||||
* cstage is correct at all of them — it keys on type_chase_named(base->
|
||||
* type)->fields / lu->size (the checker-STAMPED type), never a name
|
||||
* re-lookup. #31 aligns wwstage UP (ww-only change; cstage cgen.c is the
|
||||
* untouched oracle).
|
||||
*
|
||||
* GATE-BLIND in the bootstrap (same class as 793/784): the trigger needs
|
||||
* a SECOND module contributing a same-leaf struct of a DIFFERENT size so
|
||||
* the name-keyed lookup mis-resolves. The reddening surface is INFERRED
|
||||
* LOCALS (a global struct decl is always explicitly qualified, so its
|
||||
* qualifier resolves correctly — non-reddenable; the global value/ptr
|
||||
* arms R2/R3/A3/W3/W4a are converted-for-construction and ride the
|
||||
* corpus byte-id, not pinned here).
|
||||
*
|
||||
* row | m1 (local) | m2 (foreign) | arm | exit
|
||||
* ----------------+-------------------+--------------+-----+-----
|
||||
* ptrread_t16 | pair{u64,u16} 16B | pair 24B | R1 | 107
|
||||
* letcopy_t16 | pair{u64,u16} 16B | pair 24B | C1 | 107 (value coincides; byte-id is the net)
|
||||
* addrptr_pq | pq{u64,u64} 16B | pq 24B | A1 | 10
|
||||
* addrval_pq | pq{u64,u64} 16B | pq 24B | A2 | 10
|
||||
*
|
||||
* Each row reddens under an INDEPENDENT revert of its arm (verified
|
||||
* impl-side: R1 ww=101, C1 cs!=ww OOB, A1/A2 ww=8). cstage `ww build
|
||||
* --sep` + run pins runtime; the wwstage binary is run too (the bug WAS a
|
||||
* wrong wwstage runtime value / OOB); raw cs.s vs ww.s over the
|
||||
* driver-produced per-package asm pins rule-10 byte-id.
|
||||
*
|
||||
* NOTE the addr rows use an all-u64 `pq` (offset-only collision): the
|
||||
* canonical {u64,u16} field would, after `&p.lo`, force a `*q = v:u16`
|
||||
* narrow deref-STORE which wwstage currently emits as MOVQ where cstage
|
||||
* emits MOVW — a SEPARATE pre-existing wwstage narrow-deref-store-width
|
||||
* divergence (NOT #31; surfaced + filed by impl-31). An all-u64 store is
|
||||
* MOVQ in both stages, so the row isolates the #31 offset fix.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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);
|
||||
int cb = fgetc(fb);
|
||||
if (ca != cb) { rc = -1; break; }
|
||||
if (ca == EOF) break;
|
||||
}
|
||||
fclose(fa); fclose(fb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct file { const char *name; const char *src; };
|
||||
|
||||
struct scenario {
|
||||
const char *label;
|
||||
const struct file *files; /* name==NULL terminates */
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
/* m2 contributes 24B same-leaf `pair` and `pq` — the name-keyed lookup's
|
||||
* any-module fallback mis-resolves m1's 16B versions to these. */
|
||||
static const char m2_src[] =
|
||||
"package m2;\n"
|
||||
"export type pair = struct { hi: u64, mid: u64, lo: u64 };\n"
|
||||
"export type pq = struct { a: u64, b: u64, v: u64 };\n"
|
||||
"export fn use2(p: pair) i32 = { return (p.hi + p.mid + p.lo): i32; };\n";
|
||||
|
||||
/* m1: 16B `pair` (sub-8 tail) + 16B all-u64 `pq` (offset-only). */
|
||||
static const char m1_src[] =
|
||||
"package m1;\n"
|
||||
"export type pair = struct { hi: u64, lo: u16 };\n"
|
||||
"export type pq = struct { a: u64, v: u64 };\n"
|
||||
"let g: pair = pair { hi = 100: u64, lo = 7: u16 };\n"
|
||||
"let gq: pq = pq { a = 1: u64, v = 7: u64 };\n"
|
||||
"export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n"
|
||||
"export fn mkp() *pair = { return &g; };\n"
|
||||
"export fn mkq() pq = { return pq { a = 1: u64, v = 7: u64 }; };\n"
|
||||
"export fn mkpq() *pq = { return &gq; };\n";
|
||||
|
||||
/* ---- ptrread_t16 (R1): inferred-let *struct field read ------------- */
|
||||
static const struct file ptrread_files[] = {
|
||||
{ "m1.ww", m1_src },
|
||||
{ "m2.ww", m2_src },
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import m1;\n"
|
||||
"import m2;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let dummy: m2.pair;\n"
|
||||
" dummy.hi = 0: u64;\n"
|
||||
" let p = m1.mkp();\n"
|
||||
" return (p.hi + (p.lo: u64)): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- letcopy_t16 (C1): inferred-let local-to-local struct copy ----- */
|
||||
static const struct file letcopy_files[] = {
|
||||
{ "m1.ww", m1_src },
|
||||
{ "m2.ww", m2_src },
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import m1;\n"
|
||||
"import m2;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let dummy: m2.pair;\n"
|
||||
" dummy.hi = 0: u64;\n"
|
||||
" let p1 = m1.mk();\n"
|
||||
" let p2 = p1;\n"
|
||||
" return (p2.hi + (p2.lo: u64)): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- addrptr_pq (A1): &p.f over an inferred-let *struct ------------ */
|
||||
static const struct file addrptr_files[] = {
|
||||
{ "m1.ww", m1_src },
|
||||
{ "m2.ww", m2_src },
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import m1;\n"
|
||||
"import m2;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let dummy: m2.pq;\n"
|
||||
" dummy.a = 0: u64;\n"
|
||||
" let p = m1.mkpq();\n"
|
||||
" let q = &p.v;\n"
|
||||
" *q = 9: u64;\n"
|
||||
" return (p.a + p.v): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- addrval_pq (A2): &o.f over an inferred-let value struct ------- */
|
||||
static const struct file addrval_files[] = {
|
||||
{ "m1.ww", m1_src },
|
||||
{ "m2.ww", m2_src },
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import m1;\n"
|
||||
"import m2;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let dummy: m2.pq;\n"
|
||||
" dummy.a = 0: u64;\n"
|
||||
" let s = m1.mkq();\n"
|
||||
" let q = &s.v;\n"
|
||||
" *q = 9: u64;\n"
|
||||
" return (s.a + s.v): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const struct scenario scenarios[] = {
|
||||
{ "ptrread_t16", ptrread_files, 107 },
|
||||
{ "letcopy_t16", letcopy_files, 107 },
|
||||
{ "addrptr_pq", addrptr_files, 10 },
|
||||
{ "addrval_pq", addrval_files, 10 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_scenario(const char *cdrv, const char *wdrv, const struct scenario *sc)
|
||||
{
|
||||
char dir[] = "/tmp/ww794_XXXXXX";
|
||||
if (mkdtemp(dir) == NULL) {
|
||||
fprintf(stderr, "794[%s]: mkdtemp failed\n", sc->label);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char path[1024], cmd[4096];
|
||||
int rc = 0;
|
||||
|
||||
for (int i = 0; sc->files[i].name; i++) {
|
||||
snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) { fprintf(stderr, "794[%s]: write %s\n", sc->label,
|
||||
sc->files[i].name); rc = -1; goto done; }
|
||||
fputs(sc->files[i].src, f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && WW_PKGCACHE=%s/pkgc_c %s build --sep -I %s -o %s/main %s/main.ww",
|
||||
dir, dir, cdrv, dir, dir, dir);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "794[%s]: cstage build failed\n", sc->label);
|
||||
rc = -1; goto done;
|
||||
}
|
||||
snprintf(path, sizeof path, "%s/main", dir);
|
||||
int gotc = runwait(path);
|
||||
if (gotc != sc->want_exit) {
|
||||
fprintf(stderr, "794[%s]: cstage exit %d, want %d\n",
|
||||
sc->label, gotc, sc->want_exit);
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && WW_PKGCACHE=%s/pkgc_w %s build --sep -I %s -o %s/mainww %s/main.ww",
|
||||
dir, dir, wdrv, dir, dir, dir);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "794[%s]: ww_ww build failed\n", sc->label);
|
||||
rc = -1; goto done;
|
||||
}
|
||||
snprintf(path, sizeof path, "%s/mainww", dir);
|
||||
int gotw = runwait(path);
|
||||
if (gotw != sc->want_exit) {
|
||||
fprintf(stderr, "794[%s]: wwstage exit %d, want %d\n",
|
||||
sc->label, gotw, sc->want_exit);
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
char cs_s[1024], ws_s[1024];
|
||||
snprintf(cs_s, sizeof cs_s, "%s/all_cs.s", dir);
|
||||
snprintf(ws_s, sizeof ws_s, "%s/all_ww.s", dir);
|
||||
snprintf(cmd, sizeof cmd, "cat %s/main.sepwork/*.s > %s 2>/dev/null",
|
||||
dir, cs_s); if (system(cmd)) {}
|
||||
snprintf(cmd, sizeof cmd, "cat %s/mainww.sepwork/*.s > %s 2>/dev/null",
|
||||
dir, ws_s); if (system(cmd)) {}
|
||||
if (slurp_eq(cs_s, ws_s) != 0) {
|
||||
fprintf(stderr, "794[%s]: cs.s/ww.s DIFFER (rule-10 byte-id "
|
||||
"violation — #31 regression)\n", sc->label);
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
done:
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
||||
(void)runwait(cmd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2048];
|
||||
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[2100], wdrv[2100];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
if (access(wdrv, X_OK) != 0) {
|
||||
fprintf(stderr, "794: ww_ww missing — cannot run the cs==ww "
|
||||
"byte-id gate (the whole point of this test)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int n = (int)(sizeof scenarios / sizeof scenarios[0]);
|
||||
int fail = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (run_scenario(cdrv, wdrv, &scenarios[i]) != 0)
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "794 xmod_struct_field_layout_collide: %d/%d "
|
||||
"scenarios failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("xmod_struct_field_layout_collide: %d/%d ok (cstage+wwstage run + "
|
||||
"cs==ww byte-id)\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user