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:
11
Makefile
11
Makefile
@@ -430,6 +430,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_amp_fn_assign_run \
|
||||
$(BIN)/test_type_value_shadow_run \
|
||||
$(BIN)/test_xmod_alias_struct_collide_run \
|
||||
$(BIN)/test_xmod_struct_argpush_collide_run \
|
||||
$(BIN)/test_xmod_variant_match \
|
||||
$(BIN)/test_xmod_qualstructlit_run \
|
||||
$(BIN)/test_spread_variant_match \
|
||||
@@ -1521,6 +1522,16 @@ $(BIN)/test_xmod_alias_struct_collide_run: test/wcc/784_xmod_alias_struct_collid
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 793 #21/#224: inferred-let struct LOCAL whose leaf collides cross-module
|
||||
# miscompiled the by-value arg push + recv + direct field read (all
|
||||
# name-keyed); fix routes them off the stamped tinfo. Own 2-module fixtures
|
||||
# in a private mktemp dir — not a selfhost-driver test.
|
||||
$(BIN)/test_xmod_struct_argpush_collide_run: test/wcc/793_xmod_struct_argpush_collide_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 $@ $<
|
||||
|
||||
# #13: cross-module decomposition of an imported union's variants
|
||||
# (`case pkg.a`). Pre-fix the wwstage checker rejected it; cstage built
|
||||
# it. cstage driver build + run pins routing; ww-sep build (w6c_ww must
|
||||
|
||||
@@ -3512,9 +3512,13 @@ fn cgdot(c: *cgen, n: *syntax.node) void = {
|
||||
// same-module struct (a genuine struct-value receiver); a
|
||||
// same-module alias keeps peeling; a foreign leaf (in
|
||||
// neither registry for c.curmod) falls back to the prior
|
||||
// any-module heuristic. The broader cross-module same-leaf
|
||||
// STRUCT name-keying at the direct-struct arm below is
|
||||
// filed separately as #224 (not FLIP-triggered).
|
||||
// any-module heuristic. #21/#224: the direct-struct arm
|
||||
// below no longer name-keys — it resolves field offsets off
|
||||
// the stamped struct tinfo (dotlhs.type_), closing the
|
||||
// cross-module same-leaf STRUCT mis-read there. This peel +
|
||||
// same-module break still scopes the *struct (N_TPTR) and
|
||||
// array arms, which remain structlookupchain-keyed (out of
|
||||
// the #21 scoped slice).
|
||||
for (tn != nil && tn.kind == syntax.nkind.N_TNAME) {
|
||||
if (structsamemod(c, tn.str) != nil) { break; };
|
||||
let nx: *syntax.node = aliassamemod(c, tn.str);
|
||||
@@ -3567,69 +3571,77 @@ fn cgdot(c: *cgen, n: *syntax.node) void = {
|
||||
};
|
||||
// Direct struct local: field load at off+foff.
|
||||
if (lkind == syntax.nkind.N_TNAME) {
|
||||
// structlookupchain walks the alias chain on
|
||||
// miss so a transitively-aliased struct (`type
|
||||
// b = a; a = struct`) still resolves to the
|
||||
// underlying fieldinfo (#22).
|
||||
let si: *structinfo = structlookupchain(c, tn);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (syntax.streq(fn_, fld)) {
|
||||
// tagged-union field: emit the AX=tag,
|
||||
// DX=word0, CX=word1[, R8=word2] load
|
||||
// sequence so the match / let-init /
|
||||
// call-arg consumers see the same shape
|
||||
// as a tagged-returning fn. Pre-#28 fell
|
||||
// through to the scalar fieldloadop and
|
||||
// only AX (tag) was loaded — payload
|
||||
// words came from whatever the caller
|
||||
// left in DX/CX/R8.
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
let tsz: i32 = slotsize(c, fi.tnode);
|
||||
// #21/#224: resolve the field OFFSET + field type off
|
||||
// the checker-STAMPED struct tinfo
|
||||
// (tichase(dotlhs.type_).fields), NOT the name-keyed
|
||||
// structlookupchain(tn). On a cross-module same-leaf
|
||||
// collision lc.tnode is a bare leaf that structlookup
|
||||
// mis-resolves to a FOREIGN same-leaf struct → fields
|
||||
// read at the WRONG offsets / wrong load-op (the #224
|
||||
// direct-struct arm flagged at the peel-loop comment
|
||||
// above). The stamped tinfo carries the right layout
|
||||
// regardless of leaf collision; mirrors cstage's
|
||||
// `t->fields` walk (cgen.c N_DOT, type-keyed) — align
|
||||
// ww UP. tfield {name, type_, offset} is the tinfo twin
|
||||
// of fieldinfo {fname, tnode, foff}; the dispatch keys
|
||||
// off syntax.typeis* on the field tinfo, byte-id with
|
||||
// the prior is*type(fi.tnode)=typeis*(fi.tnode.type_).
|
||||
let sbu: *syntax.tinfo = nil;
|
||||
if (dotlhs != nil) { sbu = tichase(dotlhs.type_: *syntax.tinfo); };
|
||||
if (sbu != nil) { if (sbu.kind == syntax.tykind.TY_STRUCT) {
|
||||
let tf: *syntax.tfield = sbu.fields;
|
||||
for (tf != nil) {
|
||||
if (syntax.streq(tf.name, fld)) {
|
||||
let foff: i32 = tf.offset: i32;
|
||||
let ftraw: *syntax.tinfo = tf.type_;
|
||||
// tagged-union field: AX=tag, DX=word0,
|
||||
// CX=word1[, R8=word2]; slot = ti.size
|
||||
// (slotsize's TAGGED arm, cgenutil.ww:2680).
|
||||
if (syntax.typeistagged(ftraw)) {
|
||||
let ftc: *syntax.tinfo = tichase(ftraw);
|
||||
let tsz: i32 = 0;
|
||||
if (ftc != nil) { tsz = ftc.size: i32; };
|
||||
cgloadtaggedfield(c, "BP",
|
||||
lc.off + fi.foff, tsz, true);
|
||||
lc.off + foff, tsz, true);
|
||||
return;
|
||||
};
|
||||
// str IS []u8 — same 3-word {ptr,len,cap}
|
||||
// as a slice field: load (ptr, len, cap)
|
||||
// into (AX, BX, CX). Base is BP so no
|
||||
// aliasing — order doesn't matter. str
|
||||
// folds onto the slice arm (#1/Phase 3
|
||||
// collapse; cite cstage cgen.c N_DOT S1).
|
||||
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||
// str IS []u8 — 3-word {ptr,len,cap} into
|
||||
// (AX,BX,CX). Base is BP so order is harmless.
|
||||
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + fi.foff + 8): i64);
|
||||
emitoff((lc.off + foff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + fi.foff + 16): i64);
|
||||
emitoff((lc.off + foff + 16): i64);
|
||||
emitline("(BP), CX\n");
|
||||
} else { if (isfloattype(c, fi.tnode)) {
|
||||
} else { if (syntax.typeisfloat(ftraw)) {
|
||||
// f64/f32 field: route through X0.
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), X0\n");
|
||||
} else {
|
||||
let op: str = fieldloadop(c, fi);
|
||||
let ftc: *syntax.tinfo = tichase(ftraw);
|
||||
let fsz: i32 = 0;
|
||||
if (ftc != nil) { fsz = ftc.size: i32; };
|
||||
let op: str = loadopsz(syntax.typeissigned(ftraw), fsz);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
}; };
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
}; };
|
||||
};
|
||||
// Array pseudo-fields: `.ptr` is the array's
|
||||
// address (LEAQ); `.len` is the static element
|
||||
|
||||
@@ -2583,29 +2583,38 @@ fn cgletbody(c: *cgen, n: *syntax.node, off: i32) void = {
|
||||
};
|
||||
};
|
||||
if (rhs.kind == syntax.nkind.N_CALL) {
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == syntax.nkind.N_TNAME) {
|
||||
sname = tn.str;
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let lsi: *structinfo = structlookup(c, sname);
|
||||
if (lsi != nil) {
|
||||
// ≤24B register RECV: the value arrives packed
|
||||
// in AX/DX/CX, so size by the maxalign-rounded
|
||||
// ABI size (cstage lu->size), not the natural
|
||||
// extent — see structabisize (#169).
|
||||
let lsz: i32 = structabisize(lsi);
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (lsz <= 24) {
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, rhs);
|
||||
cgaggregstore(c, "BP", off, lsz, true);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
//
|
||||
// #21/#224: size off the checker-STAMPED tinfo
|
||||
// (structabisizetn = tichase(tn.type_).size, the
|
||||
// maxalign-rounded ABI size = check.ww:2467),
|
||||
// NOT structlookup(tn.str). On a cross-module
|
||||
// same-leaf collision the inferred-let's tn.str
|
||||
// is a bare leaf that structlookup mis-resolves to
|
||||
// a FOREIGN same-leaf struct → the recv copied that
|
||||
// struct's word count (a 24B foreign over-copies a
|
||||
// 16B local, spilling into a neighbour slot). The
|
||||
// stamped tinfo carries the right size regardless of
|
||||
// collision; byte-id with structabisize on a
|
||||
// resolving lookup. Mirrors the sibling array arm
|
||||
// below (already tn.type_-keyed). cstage is
|
||||
// type-keyed (lu->size) — align ww UP.
|
||||
let lsz: i32 = structabisizetn(tn.type_: *syntax.tinfo);
|
||||
if (lsz > 0) {
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (lsz <= 24) {
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, rhs);
|
||||
cgaggregstore(c, "BP", off, lsz, true);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -844,8 +844,18 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
|
||||
// `cgexpr(c, arg)` + scalar PUSHQ AX — only the first
|
||||
// 8B word made it across, and the callee's second-arg
|
||||
// slots picked up the wrong neighbour's value.
|
||||
let stsz: i32 = structparamsize(c, lc.tnode);
|
||||
if (stsz > 0) {
|
||||
// #21/#224: COUNT the push off the checker-STAMPED tinfo
|
||||
// (structabisizetn), not the name-keyed structparamsize(lc.tnode).
|
||||
// On a cross-module same-leaf collision the inferred-let's tnode
|
||||
// is a bare leaf that structlookup mis-resolves (returns 0 or a
|
||||
// foreign struct >16B → 0), so the fast path was skipped and the
|
||||
// arg dropped to the scalar single-PUSHQ default — word1 lost.
|
||||
// cstage counts via struct_arg_size(args[i]->type) (TYPE-keyed),
|
||||
// never colliding; align ww UP. The <=16B gate keeps >16B structs
|
||||
// + arrays on the #271 aggregate arm below (the fast path emits
|
||||
// at most 2 words).
|
||||
let stsz: i32 = structabisizetn(arg.type_: *syntax.tinfo);
|
||||
if (stsz > 0 && stsz <= 16) {
|
||||
if (stsz > 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
@@ -951,14 +961,15 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
|
||||
// aggregate) and stack-imbalanced against the type-based drain.
|
||||
let aggsz: i32 = aggargsizetn(arg.type_: *syntax.tinfo);
|
||||
if (aggsz > 0) {
|
||||
// Exclude a ≤16B-struct IDENT — it owns the structparamsize
|
||||
// fast path above (or, when a cross-module same-leaf collision
|
||||
// makes the name-keyed structparamsize miss it, the scalar
|
||||
// default below, byte-id with cstage's 1-word struct push;
|
||||
// #784/#223). The exclusion is TYPE-keyed via the stamped
|
||||
// tinfo, mirroring cstage node_isstructarg (struct_arg_size on
|
||||
// args[i]->type) — a name-keyed gate here re-opens the #211/#13
|
||||
// name-keyed divergence the cstage type gate doesn't have.
|
||||
// Exclude a ≤16B-struct IDENT — it owns the structabisizetn
|
||||
// fast path above. That path is now TYPE-keyed (#21/#224): a
|
||||
// cross-module same-leaf collision no longer misses (the prior
|
||||
// name-keyed structparamsize returned 0 → the arg dropped to the
|
||||
// scalar default → word1 lost; #784/#223). The exclusion is
|
||||
// TYPE-keyed via the stamped tinfo, mirroring cstage
|
||||
// node_isstructarg (struct_arg_size on args[i]->type) — a
|
||||
// name-keyed gate here re-opens the #211/#13 name-keyed
|
||||
// divergence the cstage type gate doesn't have.
|
||||
let structident: bool = false;
|
||||
if (arg.kind == syntax.nkind.N_IDENT) {
|
||||
let st: *syntax.tinfo = arg.type_: *syntax.tinfo;
|
||||
@@ -2046,6 +2057,28 @@ fn structabisize(si: *structinfo) i32 = {
|
||||
return (n + maxaln - 1) & ~(maxaln - 1);
|
||||
};
|
||||
|
||||
// structabisizetn — the maxalign-rounded ABI size of a by-value STRUCT,
|
||||
// read off the checker-STAMPED tinfo (.size), else 0. The tinfo twin of
|
||||
// structabisize(*structinfo): check.ww:2467 computes the struct's
|
||||
// `r.size = (off+maxalign-1)&~(maxalign-1)` — identical to structabisize's
|
||||
// formula — so `tichase(t).size` IS the ABI size, byte-id with the
|
||||
// name-keyed structabisize on a resolving lookup. The #21/#224 choke-point
|
||||
// for the caller-side register-ABI sites (struct-arg push, inferred-let
|
||||
// struct call-result recv) that previously keyed the COUNT through a
|
||||
// name-keyed structlookup/structparamsize: a cross-module same-leaf
|
||||
// collision makes that lookup return 0 (or a foreign struct's size), so the
|
||||
// push under-counted and the recv over-copied. The stamped tinfo carries
|
||||
// the right size regardless of leaf collision (the documented #211/#13/#784
|
||||
// name-keyed cluster; align wwstage UP to cstage's type-keyed struct_arg_size
|
||||
// / lu->size). STRUCT-only — arrays own their own #271/#267 arms.
|
||||
fn structabisizetn(t: *syntax.tinfo) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
let u: *syntax.tinfo = tichase(t);
|
||||
if (u == nil) { return 0; };
|
||||
if (u.kind == syntax.tykind.TY_STRUCT) { return u.size: i32; };
|
||||
return 0;
|
||||
};
|
||||
|
||||
// sretretsize — if `t` ultimately denotes a plain TY_STRUCT > 24B,
|
||||
// return its natural size; else 0. Tagged unions, tuples, str,
|
||||
// slices, scalars route through their existing register-return ABIs
|
||||
|
||||
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