cgen: type-key wwstage struct-layout at 3 sites via stamped tinfo (#21)
wwstage cgen resolved struct LAYOUT by bare-leaf name (structlookup / structparamsize) at three caller-side sites — the by-value arg push (cgenutil), the let-receive copy width (cgenstmt), and the field-read offset (cgenexpr). Under a cross-module same-leaf collision (two modules each exporting a `pair`, 16B vs 24B) the name lookup first-matches the WRONG type, so the push dropped the 2nd eightbyte, the receive over-copied, and the field read the wrong offset. cstage type-keys off the stamped tinfo and is correct; this aligns wwstage UP to it (ww-only change). Route all three sites through the stamped node.type_ via a new structabisizetn(*tinfo) accessor (push + receive) and tichase(type_).fields (field-read, structlookupchain removed). One commit (rule-11 carve-out): the collision drives all three at once and no per-site fixture isolates, so it cannot bisect-split. A scoped slice of the #209/#211 name-keyed-cgen cluster retirement; the cgdot *struct-ptr/global and let-copy siblings stay name-keyed and are filed (#31). New table-driven test 793_xmod_struct_argpush_collide_run (4 scenarios: push/recv/field over 16B and 12B tails) reddens under a revert of the three cgen files. Full make test green (336 passed); make sizelint clean.
This commit is contained in:
301
test/wcc/793_xmod_struct_argpush_collide_run.c
Normal file
301
test/wcc/793_xmod_struct_argpush_collide_run.c
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 793_xmod_struct_argpush_collide_run — project #21/#224 runtime + byte-id
|
||||
* net. An inferred-let struct LOCAL whose type leaf collides with a
|
||||
* DIFFERENT module's same-leaf struct miscompiled in wwstage at THREE
|
||||
* caller-side register-ABI sites, all keyed off the name-resolved
|
||||
* (collision-prone) struct instead of the checker-STAMPED tinfo:
|
||||
*
|
||||
* - push (cgenutil.ww pushargsrev): the by-value struct call-arg
|
||||
* COUNT came from name-keyed structparamsize(lc.tnode). On the
|
||||
* collision lc.tnode is a bare leaf structlookup mis-resolves to the
|
||||
* foreign same-leaf struct (>16B → 0), so the fast path was skipped
|
||||
* and the arg dropped to a scalar single-PUSHQ — the 2nd eightbyte
|
||||
* (the sub-8 tail) was lost. [the original #21 site]
|
||||
* - recv (cgenstmt.ww cgletbody): `let s = m1.mk()` sized the
|
||||
* register receive via structlookup(tn.str) → the foreign struct's
|
||||
* word count, OVER-copying a 24B foreign over a 16B local (spilling
|
||||
* CX into a neighbour slot).
|
||||
* - field (cgenexpr.ww cgdot direct-struct arm): `s.<field>` resolved
|
||||
* the offset via structlookupchain(tn) → the foreign struct's layout,
|
||||
* reading the field at the WRONG offset + wrong load-op. [#224, filed
|
||||
* not-fixed by 784/#223]
|
||||
*
|
||||
* cstage is correct at all three — it keys on args[i]->type / lu->size /
|
||||
* t->fields (the checker-STAMPED type), never a name re-lookup. Fix
|
||||
* (wwstage-only align-UP, #21/#224, ONE commit): route all three off the
|
||||
* stamped tinfo — structabisizetn(arg/tn.type_) for the push+recv COUNT,
|
||||
* tichase(dotlhs.type_).fields (tfield) for the field offset+type.
|
||||
*
|
||||
* GATE-BLIND in the bootstrap (same class as 784/#223): the trigger needs
|
||||
* a SECOND module contributing a same-leaf struct of a DIFFERENT size, so
|
||||
* the name-keyed lookup mis-resolves. A single-module synthetic resolves
|
||||
* correctly (structlookup's same-module pass) and stays byte-id — it does
|
||||
* NOT redden. Hence this 2-module probe.
|
||||
*
|
||||
* row | m1.pair (local) | m2.pair (foreign) | site(s) | exit
|
||||
* -----------------+----------------------+-------------------+---------------+-----
|
||||
* recvpush_t16 | {u64,u16} 16B | 24B | recv + push | 107
|
||||
* fieldread_t16 | {u64,u16} 16B | 24B | recv + field | 107
|
||||
* combined_t16 | {u64,u16} 16B | 24B | recv+field+push| 114
|
||||
* recvpush_t12 | {u32,u32,u32} 12B | 24B | recv + push | 6
|
||||
*
|
||||
* cstage `ww build --sep` + run pins runtime; the wwstage binary is run
|
||||
* too (the bug WAS a wrong wwstage runtime value); raw cs.s vs ww.s cmp
|
||||
* over the driver-produced per-package asm pins rule-10 byte-id (the
|
||||
* discriminating net — pre-fix __root.s diverges at the push/recv/field).
|
||||
*/
|
||||
#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.pair is a 24B struct sharing the leaf `pair` with m1.pair — the
|
||||
* name-keyed lookup's any-module fallback mis-resolves m1.pair to it. */
|
||||
static const char m2_24[] =
|
||||
"package m2;\n"
|
||||
"export type pair = struct { hi: u64, mid: u64, lo: u64 };\n"
|
||||
"export fn use2(p: pair) i32 = { return (p.hi + p.mid + p.lo): i32; };\n";
|
||||
|
||||
/* ---- recvpush_t16: inferred-let recv + by-value push (the #21 site) -- */
|
||||
static const struct file recvpush_t16_files[] = {
|
||||
{ "m1.ww",
|
||||
"package m1;\n"
|
||||
"export type pair = struct { hi: u64, lo: u16 };\n"
|
||||
"export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n"
|
||||
"export fn consume(p: pair) i32 = { return (p.hi + (p.lo: u64)): i32; };\n" },
|
||||
{ "m2.ww", m2_24 },
|
||||
{ "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 s = m1.mk();\n"
|
||||
" return m1.consume(s);\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- fieldread_t16: inferred-let recv + direct field read (#224) ----- */
|
||||
static const struct file fieldread_t16_files[] = {
|
||||
{ "m1.ww",
|
||||
"package m1;\n"
|
||||
"export type pair = struct { hi: u64, lo: u16 };\n"
|
||||
"export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n" },
|
||||
{ "m2.ww", m2_24 },
|
||||
{ "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 s = m1.mk();\n"
|
||||
" return (s.hi + (s.lo: u64)): i32;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- combined_t16: recv + field + push in one fn -------------------- */
|
||||
static const struct file combined_t16_files[] = {
|
||||
{ "m1.ww",
|
||||
"package m1;\n"
|
||||
"export type pair = struct { hi: u64, lo: u16 };\n"
|
||||
"export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n"
|
||||
"export fn consume(p: pair) i32 = { return (p.hi + (p.lo: u64)): i32; };\n" },
|
||||
{ "m2.ww", m2_24 },
|
||||
{ "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 s = m1.mk();\n"
|
||||
" let x: i32 = (s.lo: i32);\n"
|
||||
" return m1.consume(s) + x;\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* ---- recvpush_t12: a maxalign-4 12B sub-8-tail tail shape ----------- */
|
||||
static const struct file recvpush_t12_files[] = {
|
||||
{ "m1.ww",
|
||||
"package m1;\n"
|
||||
"export type pair = struct { a: u32, b: u32, c: u32 };\n"
|
||||
"export fn mk() pair = { return pair { a = 1: u32, b = 2: u32, c = 3: u32 }; };\n"
|
||||
"export fn consume(p: pair) i32 = { return (p.a + p.b + p.c): i32; };\n" },
|
||||
{ "m2.ww", m2_24 },
|
||||
{ "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 s = m1.mk();\n"
|
||||
" return m1.consume(s);\n"
|
||||
"};\n" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const struct scenario scenarios[] = {
|
||||
{ "recvpush_t16", recvpush_t16_files, 107 },
|
||||
{ "fieldread_t16", fieldread_t16_files, 107 },
|
||||
{ "combined_t16", combined_t16_files, 114 },
|
||||
{ "recvpush_t12", recvpush_t12_files, 6 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_scenario(const char *cdrv, const char *wdrv, const struct scenario *sc)
|
||||
{
|
||||
/* Private fixture dir — the driver's srcd-first import search can't
|
||||
* pick up a polluting same-name file (mirrors 784/#215). */
|
||||
char dir[] = "/tmp/ww793_XXXXXX";
|
||||
if (mkdtemp(dir) == NULL) {
|
||||
fprintf(stderr, "793[%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, "793[%s]: write %s\n", sc->label,
|
||||
sc->files[i].name); rc = -1; goto done; }
|
||||
fputs(sc->files[i].src, f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* cstage --sep build + run pins runtime; pin WW_PKGCACHE under the
|
||||
* scratch dir so out/.pkgcache is untouched. */
|
||||
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, "793[%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, "793[%s]: cstage exit %d, want %d\n",
|
||||
sc->label, gotc, sc->want_exit);
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
/* wwstage --sep build + run: the bug WAS a wrong wwstage runtime
|
||||
* value (a dropped/over-copied field), so run the wwstage binary too. */
|
||||
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, "793[%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, "793[%s]: wwstage exit %d, want %d\n",
|
||||
sc->label, gotw, sc->want_exit);
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
/* Byte-id net (the discriminator): per-package asm under
|
||||
* <stem>.sepwork/<pkg>.s; concat (sorted glob, identical set both
|
||||
* stages) for the compare. Pre-fix __root.s diverges. */
|
||||
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, "793[%s]: cs.s/ww.s DIFFER (rule-10 byte-id "
|
||||
"violation — #21/#224 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, "793: 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, "793 xmod_struct_argpush_collide: %d/%d "
|
||||
"scenarios failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("xmod_struct_argpush_collide: %d/%d ok (cstage+wwstage run + "
|
||||
"cs==ww byte-id)\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user