wcc/check: #62 typedecl layout is decl-order-INDEPENDENT — demand-resolve forward refs + loud cycle guard (#69)
check_file resolved typedecl bodies in file order with an eager under->size copy, so any body referencing a typedecl declared LATER read its size-0 placeholder and baked it in: alias size 0, tagged- union maxsz 0 (the F0 m5_match $48-frame under-allocated box), struct field offsets collapsed, array element stride 0 — a whole cstage-only family (7 size()-probe rows, all cs-fail/ww-pass pre-fix). wwstage's demand-driven tinfofornode was order-independent on every row, so this aligns cstage UP to the measured runtime-correct side (the #263-era ruling; rule 10's align-down governs acceptance surface, not layout correctness). Oracle: ken /tmp/ken_62_oracle.md — union size is 8B tag + roundup8(max CHASED member size), a fixed point over the module, never a function of decl order. resolve_typename now resolves a referenced-but-unresolved typedecl on demand via resolve_typedecl (cycle-guarded by Type.resolving); the pass-1.5 loop funnels through the same helper. No consumer can see an unresolved placeholder by construction. CYCLE GUARD — #69 ABSORBED into this rider (rob's rider condition): true typedecl cycles now LOUD-reject on BOTH stages — "circular type dependency" — mirroring harec's in_progress check (ref/harec/src/ check.c:4767 "Circular dependency for '%s'"). Pre-guard: cs silently sized cycles 0; wwstage HUNG on an alias cycle (`type a = b; type b = a` — ken's hang probe /tmp/ken62/c1_cycle.ww, killed at the 20s timeout) and stack-overflowed on a struct value cycle. The check sits at the VALUE-position size consumers only (alias root, struct field, array elem, tuple member, union member), so the legal pointer self-ref (`type node = struct { next: *node }`, the io.stream shape) stays accepted, byte-id. wwstage gets the twin tinfo.resolving flag (lib/ww/typ.ww) + circularnamed in check.ww; its arm loud-STOPS (os.exit) rather than accumulating — wwstage's AST-level alias walkers (resolvealias, aliaslookup chains) follow TNAME->TNAME by name, blind to the tinfo table, and spin on a cyclic alias graph even after the table edge is cut to tyerr (measured); cstage accumulates, its single-peel ternaries cannot loop. TWO-LAYER SPLIT — this is ONE bug number (#62) deliberately split across THREE commits (this rider + F1 + F2), per ken's sizes-correct ≠ payload-correct proof: in NORMAL decl order both stages size the box correctly (16/24, frames $64) yet both still run exit 2 — the box STORE is word0-only, a chase-blind copy-WIDTH lookup in cgen, NOT the type table. EXPECTED-FAIL after this commit: m5b_match1/m5_match stay exit-2 both stages (now byte-id BOTH orders; pre-fix the fwd order was $48-frame divergent). The Layer-2 sites and destinations: - F1 (cstage): cg_widen_tagged_store single NAMED peel, cmd/w6c/cgen.c ~2464 — the type_chase_named census family. - F2 (wwstage): rhsstructpayload bare name-keyed structlookup, no alias chase, selfhost/cmd/wcc/cgenutil.ww:3062 (structlookupchain :1691 already exists). Banked runtime payload-readback rows for F1/F2: /tmp/impl62r_layer2_rows.md. Test 944_alias_decl_order_size_run: every size class pinned in BOTH decl orders (sizes, named union, struct field offsets, array elem, 2-level chain — norm + fwd twins, prefix-luck-breaking last-word readbacks), 3 cycle BUILDERR rows + the legal ptr-self-ref row, (void|base) no-regress control; dual-stage + per-row byte-id (arrelem rows byte-id exempt: pre-existing #60 index-over-alias divergence, order-independent, cited at the rows). lib/ww/typ.ww is an embedded source: both main.combined.ww regen'd + committed (freshness gate).
This commit is contained in:
7
Makefile
7
Makefile
@@ -302,6 +302,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_subslice_cap_run \
|
$(BIN)/test_subslice_cap_run \
|
||||||
$(BIN)/test_subslice_ptresz_run \
|
$(BIN)/test_subslice_ptresz_run \
|
||||||
$(BIN)/test_deref_slice_store_run \
|
$(BIN)/test_deref_slice_store_run \
|
||||||
|
$(BIN)/test_alias_decl_order_size_run \
|
||||||
$(BIN)/test_tuple_nary_destructure_run \
|
$(BIN)/test_tuple_nary_destructure_run \
|
||||||
$(BIN)/test_rvalue_tuple_destructure_run \
|
$(BIN)/test_rvalue_tuple_destructure_run \
|
||||||
$(BIN)/test_overcap_tuple_field_store_run \
|
$(BIN)/test_overcap_tuple_field_store_run \
|
||||||
@@ -1446,6 +1447,12 @@ $(BIN)/test_deref_slice_store_run: test/wcc/944_deref_slice_store_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_alias_decl_order_size_run: test/wcc/944_alias_decl_order_size_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_tuple_nary_destructure_run: test/wcc/945_tuple_nary_destructure_run.c \
|
$(BIN)/test_tuple_nary_destructure_run: test/wcc/945_tuple_nary_destructure_run.c \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ lookup_builtin(const char *name)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *decl_mod(Node *file, Node *d);
|
||||||
|
static void resolve_typedecl(Checker *c, Node *d);
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
resolve_typename(Checker *c, Node *n)
|
resolve_typename(Checker *c, Node *n)
|
||||||
{
|
{
|
||||||
@@ -88,6 +91,16 @@ resolve_typename(Checker *c, Node *n)
|
|||||||
}
|
}
|
||||||
if (s == NULL || s->kind != SK_TYPE)
|
if (s == NULL || s->kind != SK_TYPE)
|
||||||
return err(c, n->pos, "unknown type '%s'", nm);
|
return err(c, n->pos, "unknown type '%s'", nm);
|
||||||
|
/* #62: a typedecl body may reference a typedecl declared LATER in
|
||||||
|
* the (driver-concatenated) file. Layout is a fixed point over the
|
||||||
|
* whole module — a function of the member types alone, never of
|
||||||
|
* decl order — so resolve the referenced decl on demand before
|
||||||
|
* handing its type out; no consumer may ever see the size-0
|
||||||
|
* placeholder. Mirrors wwstage's demand-driven tinfofornode, the
|
||||||
|
* measured order-independent side. */
|
||||||
|
if (s->type && s->type->kind == TY_NAMED && s->type->under == NULL
|
||||||
|
&& s->decl && s->decl->kind == N_TYPEDECL)
|
||||||
|
resolve_typedecl(c, s->decl);
|
||||||
return s->type;
|
return s->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,6 +606,23 @@ require_sized(Checker *c, Type *t, Pos pos, const char *where)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* circular_named — #62/#69 cycle guard: a VALUE-position reference to
|
||||||
|
* a typedecl whose body is still being resolved is a true type cycle
|
||||||
|
* (the type would have infinite size). Loud, mirroring harec's
|
||||||
|
* in_progress check (ref/harec/src/check.c:4767 "Circular dependency
|
||||||
|
* for '%s'"). Pointer/slice/chan/fn positions never read the target's
|
||||||
|
* size and legitimately receive the in-progress placeholder, so the
|
||||||
|
* check sits at the size-consuming sites only — `type node = struct {
|
||||||
|
* next: *node }` stays legal. */
|
||||||
|
static int
|
||||||
|
circular_named(Checker *c, Type *t, Pos pos)
|
||||||
|
{
|
||||||
|
if (t == NULL || t->kind != TY_NAMED || !t->resolving) return 0;
|
||||||
|
err(c, pos, "circular type dependency: '%s'",
|
||||||
|
t->name ? t->name : "?");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
resolve_type(Checker *c, Node *n)
|
resolve_type(Checker *c, Node *n)
|
||||||
{
|
{
|
||||||
@@ -633,6 +663,8 @@ resolve_type(Checker *c, Node *n)
|
|||||||
err(c, n->pos, "array length must be an integer literal");
|
err(c, n->pos, "array length must be an integer literal");
|
||||||
}
|
}
|
||||||
Type *elem = resolve_type(c, n->lhs);
|
Type *elem = resolve_type(c, n->lhs);
|
||||||
|
if (circular_named(c, elem, n->pos)) /* #62/#69 */
|
||||||
|
return ty_err;
|
||||||
require_sized(c, elem, n->pos, "an array element"); /* #108(b) */
|
require_sized(c, elem, n->pos, "an array element"); /* #108(b) */
|
||||||
return type_array(c->a, elem, len);
|
return type_array(c->a, elem, len);
|
||||||
}
|
}
|
||||||
@@ -645,6 +677,8 @@ resolve_type(Checker *c, Node *n)
|
|||||||
for (Node *e = n->list; e; e = e->next) {
|
for (Node *e = n->list; e; e = e->next) {
|
||||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||||
tp->type = resolve_type(c, e);
|
tp->type = resolve_type(c, e);
|
||||||
|
if (circular_named(c, tp->type, e->pos)) /* #62/#69 */
|
||||||
|
continue;
|
||||||
/* #108(b): an unsized member would poison sz with the
|
/* #108(b): an unsized member would poison sz with the
|
||||||
* (u64)-1 sentinel. harec ref/harec/src/type_store.c:1147. */
|
* (u64)-1 sentinel. harec ref/harec/src/type_store.c:1147. */
|
||||||
if (!require_sized(c, tp->type, e->pos, "a tuple member"))
|
if (!require_sized(c, tp->type, e->pos, "a tuple member"))
|
||||||
@@ -708,6 +742,8 @@ resolve_type(Checker *c, Node *n)
|
|||||||
for (Node *e = n->list; e; e = e->next) {
|
for (Node *e = n->list; e; e = e->next) {
|
||||||
Type *vt = resolve_type(c, e);
|
Type *vt = resolve_type(c, e);
|
||||||
if (vt == ty_never) continue;
|
if (vt == ty_never) continue;
|
||||||
|
if (circular_named(c, vt, e->pos)) /* #62/#69 */
|
||||||
|
continue;
|
||||||
/* #108(b): an unsized variant has no slot in the union
|
/* #108(b): an unsized variant has no slot in the union
|
||||||
* payload. harec ref/harec/src/type_store.c:449. */
|
* payload. harec ref/harec/src/type_store.c:449. */
|
||||||
if (!require_sized(c, vt, e->pos, "a tagged union member"))
|
if (!require_sized(c, vt, e->pos, "a tagged union member"))
|
||||||
@@ -820,6 +856,8 @@ resolve_type(Checker *c, Node *n)
|
|||||||
u64 off = 0, maxalign = 1;
|
u64 off = 0, maxalign = 1;
|
||||||
for (Node *f = n->list; f; f = f->next) {
|
for (Node *f = n->list; f; f = f->next) {
|
||||||
Type *ft = resolve_type(c, f->lhs);
|
Type *ft = resolve_type(c, f->lhs);
|
||||||
|
if (circular_named(c, ft, f->pos)) /* #62/#69 */
|
||||||
|
continue;
|
||||||
/* #108(b): an unsized field would overflow the offset
|
/* #108(b): an unsized field would overflow the offset
|
||||||
* accumulator (align/size == (u64)-1); reject + skip it. */
|
* accumulator (align/size == (u64)-1); reject + skip it. */
|
||||||
if (!require_sized(c, ft, f->pos, "a struct field"))
|
if (!require_sized(c, ft, f->pos, "a struct field"))
|
||||||
@@ -2482,6 +2520,45 @@ decl_mod(Node *file, Node *d)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* resolve_typedecl — resolve d's body into its installed TY_NAMED
|
||||||
|
* placeholder. Reached from check_file's typedecl pass AND on demand
|
||||||
|
* from resolve_typename (#62): the old file-order pass let any body
|
||||||
|
* referencing a LATER typedecl read a size-0 placeholder and bake it
|
||||||
|
* into alias sizes, union maxsz, struct field offsets and array
|
||||||
|
* element strides — decl-order-dependent layout. The resolving flag
|
||||||
|
* hands self-references the placeholder, exactly where the file-order
|
||||||
|
* pass did (sound for pointer fields, which never read the target's
|
||||||
|
* size). After a completed resolve `under` is never NULL (resolve_type
|
||||||
|
* returns ty_err/ty_void on failure), so the under==NULL demand gate
|
||||||
|
* cannot re-fire on a failed body.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
resolve_typedecl(Checker *c, Node *d)
|
||||||
|
{
|
||||||
|
Type *t = d->type;
|
||||||
|
if (t == NULL || t->under != NULL || t->resolving) return;
|
||||||
|
t->resolving = 1;
|
||||||
|
const char *save = c->cur_mod;
|
||||||
|
c->cur_mod = decl_mod(c->file, d);
|
||||||
|
Type *under = resolve_type(c, d->lhs);
|
||||||
|
c->cur_mod = save;
|
||||||
|
/* Alias-root cycle (`type a = b; type b = a` / `type a = a`):
|
||||||
|
* checked BEFORE clearing the flag so self-aliases trip on their
|
||||||
|
* own in-progress mark. ty_err instead of the cyclic under keeps
|
||||||
|
* the table acyclic by construction — every later NAMED-chain
|
||||||
|
* chase loop (F1/F2's tichase family) stays terminating. */
|
||||||
|
if (circular_named(c, under, d->pos))
|
||||||
|
under = ty_err;
|
||||||
|
t->resolving = 0;
|
||||||
|
t->under = under;
|
||||||
|
if (under) {
|
||||||
|
t->size = under->size;
|
||||||
|
t->align = under->align;
|
||||||
|
t->iserror = under->iserror;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* src_imports — does the source file that contributed decl-module
|
* src_imports — does the source file that contributed decl-module
|
||||||
* `modtag` carry `use <name>;` somewhere? With driver concatenation
|
* `modtag` carry `use <name>;` somewhere? With driver concatenation
|
||||||
@@ -2609,14 +2686,7 @@ check_file(Checker *c, Node *file)
|
|||||||
}
|
}
|
||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
if (d->kind != N_TYPEDECL) continue;
|
if (d->kind != N_TYPEDECL) continue;
|
||||||
c->cur_mod = decl_mod(file, d);
|
resolve_typedecl(c, d);
|
||||||
Type *under = resolve_type(c, d->lhs);
|
|
||||||
d->type->under = under;
|
|
||||||
if (under) {
|
|
||||||
d->type->size = under->size;
|
|
||||||
d->type->align = under->align;
|
|
||||||
d->type->iserror = under->iserror;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c->cur_mod = NULL;
|
c->cur_mod = NULL;
|
||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
|
|||||||
@@ -434,6 +434,13 @@ struct Type {
|
|||||||
int variadic;
|
int variadic;
|
||||||
const char *name; /* named alias / debug */
|
const char *name; /* named alias / debug */
|
||||||
Type *under; /* underlying resolved type for NAMED */
|
Type *under; /* underlying resolved type for NAMED */
|
||||||
|
int resolving;/* TY_NAMED demand-resolution cycle guard
|
||||||
|
* (#62): a self-reference re-entering
|
||||||
|
* resolve while the body is open gets the
|
||||||
|
* placeholder, exactly as the old file-order
|
||||||
|
* pass handed it out — sound for pointer
|
||||||
|
* fields, which never read the target's
|
||||||
|
* size. */
|
||||||
int iserror;/* Hare-style `!T` error mark; propagates
|
int iserror;/* Hare-style `!T` error mark; propagates
|
||||||
* through NAMED aliases. Variants with
|
* through NAMED aliases. Variants with
|
||||||
* iserror=1 are the propagation target of
|
* iserror=1 are the propagation target of
|
||||||
|
|||||||
@@ -124,6 +124,14 @@ type tinfo = struct {
|
|||||||
// cstage Type.nullable (cmd/wcc/ww.h:430-433).
|
// cstage Type.nullable (cmd/wcc/ww.h:430-433).
|
||||||
name: str,
|
name: str,
|
||||||
under: *tinfo,
|
under: *tinfo,
|
||||||
|
resolving: i32, // #62/#69: TY_NAMED demand-resolution cycle guard.
|
||||||
|
// Mirrors cstage Type.resolving (cmd/wcc/ww.h)
|
||||||
|
// and harec idecl->in_progress (ref/harec/src/
|
||||||
|
// check.c:4767): set while the alias body
|
||||||
|
// resolves; a VALUE-position read of an
|
||||||
|
// in-progress named is a true type cycle and
|
||||||
|
// loud-rejects. Pointer positions never read
|
||||||
|
// size, so legal self-refs stay accepted.
|
||||||
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
||||||
// `size` stays natural (Hare-faithful);
|
// `size` stays natural (Hare-faithful);
|
||||||
// `slotsize` carries the slot-padded width
|
// `slotsize` carries the slot-padded width
|
||||||
|
|||||||
@@ -9510,6 +9510,14 @@ type tinfo = struct {
|
|||||||
// cstage Type.nullable (cmd/wcc/ww.h:430-433).
|
// cstage Type.nullable (cmd/wcc/ww.h:430-433).
|
||||||
name: str,
|
name: str,
|
||||||
under: *tinfo,
|
under: *tinfo,
|
||||||
|
resolving: i32, // #62/#69: TY_NAMED demand-resolution cycle guard.
|
||||||
|
// Mirrors cstage Type.resolving (cmd/wcc/ww.h)
|
||||||
|
// and harec idecl->in_progress (ref/harec/src/
|
||||||
|
// check.c:4767): set while the alias body
|
||||||
|
// resolves; a VALUE-position read of an
|
||||||
|
// in-progress named is a true type cycle and
|
||||||
|
// loud-rejects. Pointer positions never read
|
||||||
|
// size, so legal self-refs stay accepted.
|
||||||
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
||||||
// `size` stays natural (Hare-faithful);
|
// `size` stays natural (Hare-faithful);
|
||||||
// `slotsize` carries the slot-padded width
|
// `slotsize` carries the slot-padded width
|
||||||
@@ -10321,6 +10329,36 @@ fn cerr(m: str) void = {
|
|||||||
os.write(2, m.ptr, m.len: u64);
|
os.write(2, m.ptr, m.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// circularnamed — #62/#69 cycle guard, cstage circular_named twin
|
||||||
|
// (cmd/wcc/check.c): a VALUE-position read of a TY_NAMED whose body is
|
||||||
|
// still resolving is a true type cycle (infinite size) — loud, per
|
||||||
|
// harec's in_progress check (ref/harec/src/check.c:4767 "Circular
|
||||||
|
// dependency for '%s'"). Pointer/slice/chan/fn positions never read
|
||||||
|
// the target's size and legitimately receive the in-progress
|
||||||
|
// placeholder, so `type node = struct { next: *node }` stays legal.
|
||||||
|
// Pre-#62 a pure alias cycle left a CYCLIC under-chain in the table
|
||||||
|
// and every NAMED-chain chase loop downstream spun forever (the #69
|
||||||
|
// compiler hang); a struct-value cycle recursed the slot walkers to
|
||||||
|
// stack overflow.
|
||||||
|
fn circularnamed(c: *checker, t: *tinfo, n: *node) bool = {
|
||||||
|
if (t == nil) { return false; };
|
||||||
|
if (t.kind != tykind.TY_NAMED) { return false; };
|
||||||
|
if (t.resolving == 0) { return false; };
|
||||||
|
if (n != nil) { cerr(n.file); cerr(": "); };
|
||||||
|
cerr("error: circular type dependency: '");
|
||||||
|
cerr(t.name);
|
||||||
|
cerr("'\n");
|
||||||
|
c.errs += 1;
|
||||||
|
// Loud-STOP, not accumulate: wwstage's AST-level alias walkers
|
||||||
|
// (resolvealias, cgenutil aliaslookup chains) follow TNAME->TNAME
|
||||||
|
// by NAME, blind to the tinfo table — on a cyclic alias graph they
|
||||||
|
// spin forever even after the table edge is cut to tyerr (measured:
|
||||||
|
// error printed once, then hang). cstage accumulates instead — its
|
||||||
|
// single-peel ternaries can't loop. Asymmetry is deliberate; both
|
||||||
|
// stages reject with the same message + non-zero exit.
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||||
// etc. can be looked up like ordinary symbols.
|
// etc. can be looked up like ordinary symbols.
|
||||||
fn seedprimitives(c: *checker) void = {
|
fn seedprimitives(c: *checker) void = {
|
||||||
@@ -12022,7 +12060,20 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
// TTAGGED tinfocachebind cycle-break.
|
// TTAGGED tinfocachebind cycle-break.
|
||||||
let named: *tinfo = typenamed(s.name, nil);
|
let named: *tinfo = typenamed(s.name, nil);
|
||||||
s.type_ = named;
|
s.type_ = named;
|
||||||
|
named.resolving = 1;
|
||||||
let under: *tinfo = tinfofornode(c, body);
|
let under: *tinfo = tinfofornode(c, body);
|
||||||
|
// #62/#69: alias-root cycle (`type a = b;
|
||||||
|
// type b = a` / `type a = a`) — checked
|
||||||
|
// BEFORE clearing the flag so self-aliases
|
||||||
|
// trip on their own mark. tyerr instead of
|
||||||
|
// the cyclic under keeps the table ACYCLIC
|
||||||
|
// by construction: every NAMED-chain chase
|
||||||
|
// loop stays terminating. Mirrors cstage
|
||||||
|
// resolve_typedecl.
|
||||||
|
if (circularnamed(c, under, n)) {
|
||||||
|
under = c.tc.tyerr;
|
||||||
|
};
|
||||||
|
named.resolving = 0;
|
||||||
named.under = under;
|
named.under = under;
|
||||||
if (under != nil) {
|
if (under != nil) {
|
||||||
named.size = under.size;
|
named.size = under.size;
|
||||||
@@ -12063,6 +12114,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||||
};
|
};
|
||||||
let sub: *tinfo = tinfofornode(c, n.lhs);
|
let sub: *tinfo = tinfofornode(c, n.lhs);
|
||||||
|
// #62/#69: `type a = [2]a` value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, sub, n)) { sub = c.tc.tyerr; };
|
||||||
r = typearray(sub, elen);
|
r = typearray(sub, elen);
|
||||||
case nkind.N_TFN:
|
case nkind.N_TFN:
|
||||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||||
@@ -12126,6 +12179,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
let p: *node = n.list;
|
let p: *node = n.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let pt: *tinfo = tinfofornode(c, p.lhs);
|
let pt: *tinfo = tinfofornode(c, p.lhs);
|
||||||
|
// #62/#69: tuple-member value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; };
|
||||||
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
||||||
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
||||||
tet = te;
|
tet = te;
|
||||||
@@ -12180,6 +12235,11 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
for (f != nil) {
|
for (f != nil) {
|
||||||
if (f.kind == nkind.N_TFIELD) {
|
if (f.kind == nkind.N_TFIELD) {
|
||||||
let ft: *tinfo = tinfofornode(c, f.lhs);
|
let ft: *tinfo = tinfofornode(c, f.lhs);
|
||||||
|
// #62/#69: struct-field value cycle (`type s1 =
|
||||||
|
// struct { x: s2 }; type s2 = struct { x: s1 }`)
|
||||||
|
// — loud; pre-#62 this stack-overflowed the slot
|
||||||
|
// walkers. cstage twin.
|
||||||
|
if (circularnamed(c, ft, f)) { ft = c.tc.tyerr; };
|
||||||
if (ft != nil) {
|
if (ft != nil) {
|
||||||
if (ft.align > maxalign) { maxalign = ft.align; };
|
if (ft.align > maxalign) { maxalign = ft.align; };
|
||||||
if (ft.align > 0u64) {
|
if (ft.align > 0u64) {
|
||||||
@@ -12241,6 +12301,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
let v: *node = n.list;
|
let v: *node = n.list;
|
||||||
for (v != nil) {
|
for (v != nil) {
|
||||||
let vt: *tinfo = tinfofornode(c, v);
|
let vt: *tinfo = tinfofornode(c, v);
|
||||||
|
// #62/#69: union-member value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, vt, v)) { vt = c.tc.tyerr; };
|
||||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||||
let vu: *tinfo = vt;
|
let vu: *tinfo = vt;
|
||||||
if (isspread) {
|
if (isspread) {
|
||||||
|
|||||||
@@ -57,6 +57,36 @@ fn cerr(m: str) void = {
|
|||||||
os.write(2, m.ptr, m.len: u64);
|
os.write(2, m.ptr, m.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// circularnamed — #62/#69 cycle guard, cstage circular_named twin
|
||||||
|
// (cmd/wcc/check.c): a VALUE-position read of a TY_NAMED whose body is
|
||||||
|
// still resolving is a true type cycle (infinite size) — loud, per
|
||||||
|
// harec's in_progress check (ref/harec/src/check.c:4767 "Circular
|
||||||
|
// dependency for '%s'"). Pointer/slice/chan/fn positions never read
|
||||||
|
// the target's size and legitimately receive the in-progress
|
||||||
|
// placeholder, so `type node = struct { next: *node }` stays legal.
|
||||||
|
// Pre-#62 a pure alias cycle left a CYCLIC under-chain in the table
|
||||||
|
// and every NAMED-chain chase loop downstream spun forever (the #69
|
||||||
|
// compiler hang); a struct-value cycle recursed the slot walkers to
|
||||||
|
// stack overflow.
|
||||||
|
fn circularnamed(c: *checker, t: *tinfo, n: *node) bool = {
|
||||||
|
if (t == nil) { return false; };
|
||||||
|
if (t.kind != tykind.TY_NAMED) { return false; };
|
||||||
|
if (t.resolving == 0) { return false; };
|
||||||
|
if (n != nil) { cerr(n.file); cerr(": "); };
|
||||||
|
cerr("error: circular type dependency: '");
|
||||||
|
cerr(t.name);
|
||||||
|
cerr("'\n");
|
||||||
|
c.errs += 1;
|
||||||
|
// Loud-STOP, not accumulate: wwstage's AST-level alias walkers
|
||||||
|
// (resolvealias, cgenutil aliaslookup chains) follow TNAME->TNAME
|
||||||
|
// by NAME, blind to the tinfo table — on a cyclic alias graph they
|
||||||
|
// spin forever even after the table edge is cut to tyerr (measured:
|
||||||
|
// error printed once, then hang). cstage accumulates instead — its
|
||||||
|
// single-peel ternaries can't loop. Asymmetry is deliberate; both
|
||||||
|
// stages reject with the same message + non-zero exit.
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||||
// etc. can be looked up like ordinary symbols.
|
// etc. can be looked up like ordinary symbols.
|
||||||
fn seedprimitives(c: *checker) void = {
|
fn seedprimitives(c: *checker) void = {
|
||||||
@@ -1758,7 +1788,20 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
// TTAGGED tinfocachebind cycle-break.
|
// TTAGGED tinfocachebind cycle-break.
|
||||||
let named: *tinfo = typenamed(s.name, nil);
|
let named: *tinfo = typenamed(s.name, nil);
|
||||||
s.type_ = named;
|
s.type_ = named;
|
||||||
|
named.resolving = 1;
|
||||||
let under: *tinfo = tinfofornode(c, body);
|
let under: *tinfo = tinfofornode(c, body);
|
||||||
|
// #62/#69: alias-root cycle (`type a = b;
|
||||||
|
// type b = a` / `type a = a`) — checked
|
||||||
|
// BEFORE clearing the flag so self-aliases
|
||||||
|
// trip on their own mark. tyerr instead of
|
||||||
|
// the cyclic under keeps the table ACYCLIC
|
||||||
|
// by construction: every NAMED-chain chase
|
||||||
|
// loop stays terminating. Mirrors cstage
|
||||||
|
// resolve_typedecl.
|
||||||
|
if (circularnamed(c, under, n)) {
|
||||||
|
under = c.tc.tyerr;
|
||||||
|
};
|
||||||
|
named.resolving = 0;
|
||||||
named.under = under;
|
named.under = under;
|
||||||
if (under != nil) {
|
if (under != nil) {
|
||||||
named.size = under.size;
|
named.size = under.size;
|
||||||
@@ -1799,6 +1842,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||||
};
|
};
|
||||||
let sub: *tinfo = tinfofornode(c, n.lhs);
|
let sub: *tinfo = tinfofornode(c, n.lhs);
|
||||||
|
// #62/#69: `type a = [2]a` value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, sub, n)) { sub = c.tc.tyerr; };
|
||||||
r = typearray(sub, elen);
|
r = typearray(sub, elen);
|
||||||
case nkind.N_TFN:
|
case nkind.N_TFN:
|
||||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||||
@@ -1862,6 +1907,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
let p: *node = n.list;
|
let p: *node = n.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let pt: *tinfo = tinfofornode(c, p.lhs);
|
let pt: *tinfo = tinfofornode(c, p.lhs);
|
||||||
|
// #62/#69: tuple-member value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; };
|
||||||
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
||||||
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
||||||
tet = te;
|
tet = te;
|
||||||
@@ -1916,6 +1963,11 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
for (f != nil) {
|
for (f != nil) {
|
||||||
if (f.kind == nkind.N_TFIELD) {
|
if (f.kind == nkind.N_TFIELD) {
|
||||||
let ft: *tinfo = tinfofornode(c, f.lhs);
|
let ft: *tinfo = tinfofornode(c, f.lhs);
|
||||||
|
// #62/#69: struct-field value cycle (`type s1 =
|
||||||
|
// struct { x: s2 }; type s2 = struct { x: s1 }`)
|
||||||
|
// — loud; pre-#62 this stack-overflowed the slot
|
||||||
|
// walkers. cstage twin.
|
||||||
|
if (circularnamed(c, ft, f)) { ft = c.tc.tyerr; };
|
||||||
if (ft != nil) {
|
if (ft != nil) {
|
||||||
if (ft.align > maxalign) { maxalign = ft.align; };
|
if (ft.align > maxalign) { maxalign = ft.align; };
|
||||||
if (ft.align > 0u64) {
|
if (ft.align > 0u64) {
|
||||||
@@ -1977,6 +2029,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
let v: *node = n.list;
|
let v: *node = n.list;
|
||||||
for (v != nil) {
|
for (v != nil) {
|
||||||
let vt: *tinfo = tinfofornode(c, v);
|
let vt: *tinfo = tinfofornode(c, v);
|
||||||
|
// #62/#69: union-member value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, vt, v)) { vt = c.tc.tyerr; };
|
||||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||||
let vu: *tinfo = vt;
|
let vu: *tinfo = vt;
|
||||||
if (isspread) {
|
if (isspread) {
|
||||||
|
|||||||
@@ -9510,6 +9510,14 @@ type tinfo = struct {
|
|||||||
// cstage Type.nullable (cmd/wcc/ww.h:430-433).
|
// cstage Type.nullable (cmd/wcc/ww.h:430-433).
|
||||||
name: str,
|
name: str,
|
||||||
under: *tinfo,
|
under: *tinfo,
|
||||||
|
resolving: i32, // #62/#69: TY_NAMED demand-resolution cycle guard.
|
||||||
|
// Mirrors cstage Type.resolving (cmd/wcc/ww.h)
|
||||||
|
// and harec idecl->in_progress (ref/harec/src/
|
||||||
|
// check.c:4767): set while the alias body
|
||||||
|
// resolves; a VALUE-position read of an
|
||||||
|
// in-progress named is a true type cycle and
|
||||||
|
// loud-rejects. Pointer positions never read
|
||||||
|
// size, so legal self-refs stay accepted.
|
||||||
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
||||||
// `size` stays natural (Hare-faithful);
|
// `size` stays natural (Hare-faithful);
|
||||||
// `slotsize` carries the slot-padded width
|
// `slotsize` carries the slot-padded width
|
||||||
@@ -10321,6 +10329,36 @@ fn cerr(m: str) void = {
|
|||||||
os.write(2, m.ptr, m.len: u64);
|
os.write(2, m.ptr, m.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// circularnamed — #62/#69 cycle guard, cstage circular_named twin
|
||||||
|
// (cmd/wcc/check.c): a VALUE-position read of a TY_NAMED whose body is
|
||||||
|
// still resolving is a true type cycle (infinite size) — loud, per
|
||||||
|
// harec's in_progress check (ref/harec/src/check.c:4767 "Circular
|
||||||
|
// dependency for '%s'"). Pointer/slice/chan/fn positions never read
|
||||||
|
// the target's size and legitimately receive the in-progress
|
||||||
|
// placeholder, so `type node = struct { next: *node }` stays legal.
|
||||||
|
// Pre-#62 a pure alias cycle left a CYCLIC under-chain in the table
|
||||||
|
// and every NAMED-chain chase loop downstream spun forever (the #69
|
||||||
|
// compiler hang); a struct-value cycle recursed the slot walkers to
|
||||||
|
// stack overflow.
|
||||||
|
fn circularnamed(c: *checker, t: *tinfo, n: *node) bool = {
|
||||||
|
if (t == nil) { return false; };
|
||||||
|
if (t.kind != tykind.TY_NAMED) { return false; };
|
||||||
|
if (t.resolving == 0) { return false; };
|
||||||
|
if (n != nil) { cerr(n.file); cerr(": "); };
|
||||||
|
cerr("error: circular type dependency: '");
|
||||||
|
cerr(t.name);
|
||||||
|
cerr("'\n");
|
||||||
|
c.errs += 1;
|
||||||
|
// Loud-STOP, not accumulate: wwstage's AST-level alias walkers
|
||||||
|
// (resolvealias, cgenutil aliaslookup chains) follow TNAME->TNAME
|
||||||
|
// by NAME, blind to the tinfo table — on a cyclic alias graph they
|
||||||
|
// spin forever even after the table edge is cut to tyerr (measured:
|
||||||
|
// error printed once, then hang). cstage accumulates instead — its
|
||||||
|
// single-peel ternaries can't loop. Asymmetry is deliberate; both
|
||||||
|
// stages reject with the same message + non-zero exit.
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||||
// etc. can be looked up like ordinary symbols.
|
// etc. can be looked up like ordinary symbols.
|
||||||
fn seedprimitives(c: *checker) void = {
|
fn seedprimitives(c: *checker) void = {
|
||||||
@@ -12022,7 +12060,20 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
// TTAGGED tinfocachebind cycle-break.
|
// TTAGGED tinfocachebind cycle-break.
|
||||||
let named: *tinfo = typenamed(s.name, nil);
|
let named: *tinfo = typenamed(s.name, nil);
|
||||||
s.type_ = named;
|
s.type_ = named;
|
||||||
|
named.resolving = 1;
|
||||||
let under: *tinfo = tinfofornode(c, body);
|
let under: *tinfo = tinfofornode(c, body);
|
||||||
|
// #62/#69: alias-root cycle (`type a = b;
|
||||||
|
// type b = a` / `type a = a`) — checked
|
||||||
|
// BEFORE clearing the flag so self-aliases
|
||||||
|
// trip on their own mark. tyerr instead of
|
||||||
|
// the cyclic under keeps the table ACYCLIC
|
||||||
|
// by construction: every NAMED-chain chase
|
||||||
|
// loop stays terminating. Mirrors cstage
|
||||||
|
// resolve_typedecl.
|
||||||
|
if (circularnamed(c, under, n)) {
|
||||||
|
under = c.tc.tyerr;
|
||||||
|
};
|
||||||
|
named.resolving = 0;
|
||||||
named.under = under;
|
named.under = under;
|
||||||
if (under != nil) {
|
if (under != nil) {
|
||||||
named.size = under.size;
|
named.size = under.size;
|
||||||
@@ -12063,6 +12114,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||||
};
|
};
|
||||||
let sub: *tinfo = tinfofornode(c, n.lhs);
|
let sub: *tinfo = tinfofornode(c, n.lhs);
|
||||||
|
// #62/#69: `type a = [2]a` value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, sub, n)) { sub = c.tc.tyerr; };
|
||||||
r = typearray(sub, elen);
|
r = typearray(sub, elen);
|
||||||
case nkind.N_TFN:
|
case nkind.N_TFN:
|
||||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||||
@@ -12126,6 +12179,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
let p: *node = n.list;
|
let p: *node = n.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let pt: *tinfo = tinfofornode(c, p.lhs);
|
let pt: *tinfo = tinfofornode(c, p.lhs);
|
||||||
|
// #62/#69: tuple-member value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; };
|
||||||
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!;
|
||||||
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
if (teh == nil) { teh = te; } else { tet.tnext = te; };
|
||||||
tet = te;
|
tet = te;
|
||||||
@@ -12180,6 +12235,11 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
for (f != nil) {
|
for (f != nil) {
|
||||||
if (f.kind == nkind.N_TFIELD) {
|
if (f.kind == nkind.N_TFIELD) {
|
||||||
let ft: *tinfo = tinfofornode(c, f.lhs);
|
let ft: *tinfo = tinfofornode(c, f.lhs);
|
||||||
|
// #62/#69: struct-field value cycle (`type s1 =
|
||||||
|
// struct { x: s2 }; type s2 = struct { x: s1 }`)
|
||||||
|
// — loud; pre-#62 this stack-overflowed the slot
|
||||||
|
// walkers. cstage twin.
|
||||||
|
if (circularnamed(c, ft, f)) { ft = c.tc.tyerr; };
|
||||||
if (ft != nil) {
|
if (ft != nil) {
|
||||||
if (ft.align > maxalign) { maxalign = ft.align; };
|
if (ft.align > maxalign) { maxalign = ft.align; };
|
||||||
if (ft.align > 0u64) {
|
if (ft.align > 0u64) {
|
||||||
@@ -12241,6 +12301,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
|||||||
let v: *node = n.list;
|
let v: *node = n.list;
|
||||||
for (v != nil) {
|
for (v != nil) {
|
||||||
let vt: *tinfo = tinfofornode(c, v);
|
let vt: *tinfo = tinfofornode(c, v);
|
||||||
|
// #62/#69: union-member value cycle — loud, cstage twin.
|
||||||
|
if (circularnamed(c, vt, v)) { vt = c.tc.tyerr; };
|
||||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||||
let vu: *tinfo = vt;
|
let vu: *tinfo = vt;
|
||||||
if (isspread) {
|
if (isspread) {
|
||||||
|
|||||||
430
test/wcc/944_alias_decl_order_size_run.c
Normal file
430
test/wcc/944_alias_decl_order_size_run.c
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
/*
|
||||||
|
* 944_alias_decl_order_size_run — #62 rider (alias arc pre-F1): type-
|
||||||
|
* table layout must be DECL-ORDER-INDEPENDENT. cstage check_file used
|
||||||
|
* to resolve typedecl bodies in file order with an eager under->size
|
||||||
|
* copy, so any body referencing a typedecl declared LATER read its
|
||||||
|
* size-0 placeholder and baked it in: alias size 0, tagged-union
|
||||||
|
* maxsz 0 (the F0 m5_match $48-frame under-allocated box), struct
|
||||||
|
* field offsets collapsed, array element stride 0. wwstage (demand-
|
||||||
|
* driven tinfofornode) was order-independent on every row — the
|
||||||
|
* measured-sound side cstage now mirrors via resolve_typedecl's
|
||||||
|
* resolve-on-first-reference. Oracle: ken /tmp/ken_62_oracle.md
|
||||||
|
* (size((void|ali)) = 8B tag + roundup8(chased member size); every
|
||||||
|
* behavior indistinguishable from the (void|base) spelling).
|
||||||
|
*
|
||||||
|
* row | shape | want
|
||||||
|
* -----------------+-----------------------------------------+-----
|
||||||
|
* sizes_norm | base,ali decl order; size(base/ali/ |
|
||||||
|
* | (void|ali)) = 16/16/24 | 0
|
||||||
|
* sizes_fwd | ali BEFORE base (fwd-ref); same asserts | 0
|
||||||
|
* union_decl_norm | named u=(void|ali) declared LAST | 0
|
||||||
|
* union_decl_fwd | named u=(void|ali) declared FIRST | 0
|
||||||
|
* field_fwd | outer{i,j:inner} before inner — total |
|
||||||
|
* | size AND j.b offset readback (4/9/7/3 |
|
||||||
|
* | break prefix-luck; last word checked) | 0
|
||||||
|
* field_norm | inner before outer; same asserts | 0
|
||||||
|
* arrelem_fwd | arr=[2]base before base — stride + |
|
||||||
|
* | a[1].b last-word readback | 0
|
||||||
|
* arrelem_norm | base before arr; same asserts | 0
|
||||||
|
* chain2_fwd | a2=a1=base full fwd chain; 16/16 | 0
|
||||||
|
* chain2_norm | base,a1,a2 decl order; same asserts | 0
|
||||||
|
* union_base_ctl | (void|base) match payload readback — |
|
||||||
|
* | the m5b_match0 no-regress control | 0
|
||||||
|
* cycle_alias | type a=b; type b=a — loud BUILDERR |
|
||||||
|
* | both stages (#69: pre-fix cs silent-0, |
|
||||||
|
* | ww HANG) | err
|
||||||
|
* cycle_self | type a=a — loud BUILDERR both stages | err
|
||||||
|
* cycle_struct_value | s1{x:s2}/s2{x:s1} — loud BUILDERR |
|
||||||
|
* | both stages (pre-fix ww stack overflow) | err
|
||||||
|
* ptr_selfref_ok | node{v,next:*node} legal self-ref stays |
|
||||||
|
* | accepted + byte-id (guard no-over-fire) | 0
|
||||||
|
*
|
||||||
|
* NOT pinned here: the alias-in-union match payload readback
|
||||||
|
* (m5b_match1) — its word0-only box STORE is the Layer-2 cgen family
|
||||||
|
* (cstage cg_widen_tagged_store single NAMED peel; wwstage
|
||||||
|
* rhsstructpayload name-keyed structlookup without alias chase),
|
||||||
|
* EXPECTED-FAIL until the F1/F2 copy-width fix lands. See task #62.
|
||||||
|
*
|
||||||
|
* Every row also asserts cstage/wwstage asm byte-id, except rows
|
||||||
|
* flagged nobyteid (arrelem_fwd — the pre-existing task-#60 index-
|
||||||
|
* over-alias divergence; see the row comment). NNN<950,
|
||||||
|
* self-contained (/tmp, no imports) — rule-14's selfhost-sibling
|
||||||
|
* race does not apply (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
|
||||||
|
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
|
||||||
|
|
||||||
|
/* nobyteid: the row's RUNTIME behavior is pinned on both stages but
|
||||||
|
* its asm is excluded from the byte-id sweep — cite the filed task at
|
||||||
|
* the row. */
|
||||||
|
struct row { const char *label; const char *src; int want; int nobyteid;
|
||||||
|
int kind; const char *experr; };
|
||||||
|
|
||||||
|
/* errlog_has — a BUILDERR row must fail WITH its diagnostic; any other
|
||||||
|
* failure (parse error, crash, hang-kill) is a vacuous reject (940
|
||||||
|
* precedent). */
|
||||||
|
static int
|
||||||
|
errlog_has(const char *path, const char *needle)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return 0;
|
||||||
|
char buf[8192];
|
||||||
|
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[got] = '\0';
|
||||||
|
return strstr(buf, needle) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
{ "sizes_norm",
|
||||||
|
"package main;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"type ali = base;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(base) != 16) { return 1; };\n"
|
||||||
|
" if (size(ali) != 16) { return 2; };\n"
|
||||||
|
" if (size((void | ali)) != 24) { return 3; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
{ "sizes_fwd",
|
||||||
|
"package main;\n"
|
||||||
|
"type ali = base;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(base) != 16) { return 1; };\n"
|
||||||
|
" if (size(ali) != 16) { return 2; };\n"
|
||||||
|
" if (size((void | ali)) != 24) { return 3; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
{ "union_decl_norm",
|
||||||
|
"package main;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"type ali = base;\n"
|
||||||
|
"type u = (void | ali);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(u) != 24) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
{ "union_decl_fwd",
|
||||||
|
"package main;\n"
|
||||||
|
"type u = (void | ali);\n"
|
||||||
|
"type ali = base;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(u) != 24) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
/* fwd field: a size-0 inner collapsed outer's offsets too — pin
|
||||||
|
* the LAST word of the SECOND field, values all distinct. */
|
||||||
|
{ "field_fwd",
|
||||||
|
"package main;\n"
|
||||||
|
"type outer = struct { i: inner, j: inner };\n"
|
||||||
|
"type inner = struct { a: size, b: size };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(inner) != 16) { return 1; };\n"
|
||||||
|
" if (size(outer) != 32) { return 2; };\n"
|
||||||
|
" let o: outer;\n"
|
||||||
|
" o.i.a = 4; o.i.b = 9; o.j.a = 7; o.j.b = 3;\n"
|
||||||
|
" if (o.i.a != 4) { return 3; };\n"
|
||||||
|
" if (o.i.b != 9) { return 4; };\n"
|
||||||
|
" if (o.j.a != 7) { return 5; };\n"
|
||||||
|
" if (o.j.b != 3) { return 6; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
{ "field_norm",
|
||||||
|
"package main;\n"
|
||||||
|
"type inner = struct { a: size, b: size };\n"
|
||||||
|
"type outer = struct { i: inner, j: inner };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(inner) != 16) { return 1; };\n"
|
||||||
|
" if (size(outer) != 32) { return 2; };\n"
|
||||||
|
" let o: outer;\n"
|
||||||
|
" o.i.a = 4; o.i.b = 9; o.j.a = 7; o.j.b = 3;\n"
|
||||||
|
" if (o.i.a != 4) { return 3; };\n"
|
||||||
|
" if (o.i.b != 9) { return 4; };\n"
|
||||||
|
" if (o.j.a != 7) { return 5; };\n"
|
||||||
|
" if (o.j.b != 3) { return 6; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
/* nobyteid: indexing an alias-NAMED array local is the task-#60
|
||||||
|
* family — wwstage emits a ptr-load/ADDQ spine vs cstage's direct
|
||||||
|
* 8(BX); PRE-EXISTING on master, order-independent, runtime-
|
||||||
|
* correct here (8B-multiple elements). The size assert + element
|
||||||
|
* readback below stay pinned on both stages; flip this flag when
|
||||||
|
* #60 lands. */
|
||||||
|
{ "arrelem_fwd",
|
||||||
|
"package main;\n"
|
||||||
|
"type arr = [2]base;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(arr) != 32) { return 1; };\n"
|
||||||
|
" let a: arr;\n"
|
||||||
|
" a[0].a = 4; a[0].b = 9; a[1].a = 7; a[1].b = 3;\n"
|
||||||
|
" if (a[0].b != 9) { return 2; };\n"
|
||||||
|
" if (a[1].a != 7) { return 3; };\n"
|
||||||
|
" if (a[1].b != 3) { return 4; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 1, K_RUN, NULL },
|
||||||
|
/* nobyteid: same #60 cite as arrelem_fwd — the index-over-alias
|
||||||
|
* divergence is order-INDEPENDENT (verified on master). */
|
||||||
|
{ "arrelem_norm",
|
||||||
|
"package main;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"type arr = [2]base;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(arr) != 32) { return 1; };\n"
|
||||||
|
" let a: arr;\n"
|
||||||
|
" a[0].a = 4; a[0].b = 9; a[1].a = 7; a[1].b = 3;\n"
|
||||||
|
" if (a[0].b != 9) { return 2; };\n"
|
||||||
|
" if (a[1].a != 7) { return 3; };\n"
|
||||||
|
" if (a[1].b != 3) { return 4; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 1, K_RUN, NULL },
|
||||||
|
{ "chain2_fwd",
|
||||||
|
"package main;\n"
|
||||||
|
"type a2 = a1;\n"
|
||||||
|
"type a1 = base;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(a1) != 16) { return 1; };\n"
|
||||||
|
" if (size(a2) != 16) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
{ "chain2_norm",
|
||||||
|
"package main;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"type a1 = base;\n"
|
||||||
|
"type a2 = a1;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(a1) != 16) { return 1; };\n"
|
||||||
|
" if (size(a2) != 16) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
/* m5b_match0 no-regress control: direct base member, full payload
|
||||||
|
* readback through the box, last word checked. */
|
||||||
|
{ "union_base_ctl",
|
||||||
|
"package main;\n"
|
||||||
|
"type base = struct { a: size, b: size };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: base;\n"
|
||||||
|
" x.a = 4; x.b = 9;\n"
|
||||||
|
" let v: (void | base) = x;\n"
|
||||||
|
" match (v) {\n"
|
||||||
|
" case let s: base => {\n"
|
||||||
|
" if (s.a != 4) { return 1; };\n"
|
||||||
|
" if (s.b != 9) { return 2; };\n"
|
||||||
|
" };\n"
|
||||||
|
" case void => { return 3; };\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
/* #62/#69 cycle guard (rob's rider condition): TRUE typedecl
|
||||||
|
* cycles loud-reject on BOTH stages — pre-guard cs silently sized
|
||||||
|
* them 0 and wwstage HUNG (alias cycle) / stack-overflowed (struct
|
||||||
|
* value cycle). harec cite: check.c:4767 "Circular dependency". */
|
||||||
|
{ "cycle_alias",
|
||||||
|
"package main;\n"
|
||||||
|
"type a = b;\n"
|
||||||
|
"type b = a;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(a) != 8) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_BUILDERR, "circular type dependency" },
|
||||||
|
{ "cycle_self",
|
||||||
|
"package main;\n"
|
||||||
|
"type a = a;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(a) != 8) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_BUILDERR, "circular type dependency" },
|
||||||
|
{ "cycle_struct_value",
|
||||||
|
"package main;\n"
|
||||||
|
"type s1 = struct { x: s2 };\n"
|
||||||
|
"type s2 = struct { x: s1 };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (size(s1) != 8) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_BUILDERR, "circular type dependency" },
|
||||||
|
/* The LEGAL self-reference (pointer field never reads the
|
||||||
|
* target's size) must stay accepted — the io.stream / list-node
|
||||||
|
* shape the cycle guard is forbidden from breaking. */
|
||||||
|
{ "ptr_selfref_ok",
|
||||||
|
"package main;\n"
|
||||||
|
"type node = struct { v: size, next: *node };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let n: node;\n"
|
||||||
|
" n.v = 7;\n"
|
||||||
|
" n.next = &n;\n"
|
||||||
|
" if (size(node) != 16) { return 1; };\n"
|
||||||
|
" if (n.v != 7) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, 0, K_RUN, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
||||||
|
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/ados_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/ados_%d_d_%d", getpid(), i);
|
||||||
|
snprintf(errf, sizeof errf, "/tmp/ados_%d_e_%d", getpid(), i);
|
||||||
|
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
/* timeout: the pre-#69 wwstage HANG on a cycle must fail the row,
|
||||||
|
* not wedge the suite. */
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && timeout 20 %s build %s >/dev/null 2>%s",
|
||||||
|
tmpdir, driver, src, errf);
|
||||||
|
int brc = runwait(cmd);
|
||||||
|
if (r->kind == K_BUILDERR) {
|
||||||
|
int ok = (brc != 0)
|
||||||
|
&& (r->experr == NULL || errlog_has(errf, r->experr));
|
||||||
|
if (!ok)
|
||||||
|
fprintf(stderr, "row[%s]: %s expected loud builderr "
|
||||||
|
"\"%s\" (brc=%d)\n", r->label, driver,
|
||||||
|
r->experr ? r->experr : "", brc);
|
||||||
|
unlink(src); unlink(errf); rmdir(tmpdir);
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
|
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/ados_asm_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(cs, sizeof cs, "/tmp/ados_asm_%d_%d_c.s", getpid(), i);
|
||||||
|
snprintf(ws, sizeof ws, "/tmp/ados_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].nobyteid || rows[i].kind == K_BUILDERR)
|
||||||
|
continue;
|
||||||
|
total++;
|
||||||
|
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "alias_decl_order_size: %d/%d checks failed\n",
|
||||||
|
fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("alias_decl_order_size: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user