cgen: fix nested aggregate field dropped in alloc-heap structlit fill (C7c)
`alloc(Outer{ x = Inner{q=10} })` dropped the nested struct-literal field:
the alloc path had its own inline fill loop with only scalar/float/str
arms, so a field whose value is itself an N_STRUCTLIT fell to the scalar
tail and stored MOVQ $0 (cgexpr leaves a whole aggregate in no register)
over the inner slot. Both stages emitted the identical wrong fill, so the
byte-id gate was blind to it.
Route alloc's fill through the existing shared structlit-fill helper (the
one the BP-relative/global/local structlit sites already use -- it handles
nested-struct recursion, N_ARRLIT, str/slice and tagged) via a new 4th
destination mode DST_PTR_SP that reloads the heap base from (SP). This
deletes alloc's divergent inline loop, the lone site lacking the recursion.
As a side effect it also fixes a latent slice-field drop in the driver's
own alloc(sepgraph{...}) (pkg.len/.cap were dropped; the consumer reads
neither -- g.n is the count SSoT). Nested-array fields are closed in-class;
a nested tuple-LITERAL field now errors loudly and symmetrically (the #49
non-addressable gap, previously dropped silently at alloc only).
Surfaced by the codegen miscompile hunt (finding C7c). Pinned by
test/lang/alloc_nested_field_test.ww (nested struct depth 1+2, nested
array, adjacent multi-nested, sibling-no-clobber; reddens on revert).
Routing preservation proven: the whole test/lang corpus is byte-identical
HEAD vs fixed except the new pin; self-compile byte-id (990-996) green.
This commit is contained in:
@@ -2523,6 +2523,7 @@ enum {
|
||||
DST_BP = 0,
|
||||
DST_PTR_LOCAL = 1,
|
||||
DST_GLOBAL = 2,
|
||||
DST_PTR_SP = 3,
|
||||
};
|
||||
static void cg_structlit_fill(Cg*, Local**, Type*, Node*, int, int, const char*, int);
|
||||
static void cg_structlit_fill_bp(Cg*, Local**, Type*, Node*, int);
|
||||
@@ -3296,6 +3297,15 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
* DST_GLOBAL — base = BX, reloaded via `LEAQ name(SB), BX` with
|
||||
* the same reload cadence as DST_PTR_LOCAL.
|
||||
* srcoff unused.
|
||||
* DST_PTR_SP — base = BX, reloaded via `MOVQ (SP), BX` with the
|
||||
* same reload cadence as DST_PTR_LOCAL. The dst is the
|
||||
* alloc-heap base saved by the alloc(value) arm's
|
||||
* `PUSHQ AX` (top-of-stack); cgexpr is stack-balanced
|
||||
* so (SP) keeps pointing at it across the field walk.
|
||||
* srcoff/name unused. C7c: recurse into a nested
|
||||
* struct/array/tuple field VALUE so alloc(Outer{x =
|
||||
* Inner{..}}) writes the inner leaves instead of
|
||||
* storing AX=0 over the whole inner slot.
|
||||
*
|
||||
* Param semantics (locked in here so the recursion contract is clear):
|
||||
* - `disp` is the per-recursion accumulator — grows by `foff` as
|
||||
@@ -3341,6 +3351,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff), areg(D_BX));
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name), areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
int zi = 0;
|
||||
while (zi + 8 <= sz) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
@@ -3384,6 +3396,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
areg(D_BX));
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name), areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
cg_widen_tagged_store(c, locals_p, fu, f->lhs,
|
||||
base_reg, disp + (int)foff, (int)fu->size);
|
||||
continue;
|
||||
@@ -3431,6 +3445,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
areg(D_BX));
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name), areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = fsz / 8;
|
||||
int tail = fsz % 8;
|
||||
@@ -3466,6 +3482,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
areg(D_DX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0),
|
||||
areg(D_DX));
|
||||
else
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_DX));
|
||||
@@ -3524,6 +3543,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0),
|
||||
areg(D_BX));
|
||||
int eoff = disp + (int)foff + idx * esz;
|
||||
if (is_float_el)
|
||||
ins2(c, fmov, areg(D_X0),
|
||||
@@ -3543,6 +3565,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0),
|
||||
areg(D_BX));
|
||||
int eoff = disp + (int)foff + idx * esz;
|
||||
if (is_float_el)
|
||||
ins2(c, fmov, areg(D_X0),
|
||||
@@ -3581,6 +3606,9 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_BX));
|
||||
@@ -3599,6 +3627,8 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff), areg(D_BX));
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name), areg(D_BX));
|
||||
else if (mode == DST_PTR_SP)
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
int sl_isf32 = 0;
|
||||
if (fld_isfloat(ft, &sl_isf32)) {
|
||||
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
|
||||
@@ -8772,48 +8802,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
label(c, alloc_ok);
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* save ptr */
|
||||
if (v->kind == N_STRUCTLIT && u && u->kind == TY_STRUCT) {
|
||||
for (Node *f = v->list; f; f = f->next) {
|
||||
u64 foff = 0;
|
||||
int fsz = 8;
|
||||
Type *ftype = NULL;
|
||||
for (Tfield *fl = u->fields; fl; fl = fl->next) {
|
||||
if (strcmp(fl->name, f->str) == 0) {
|
||||
foff = fl->offset;
|
||||
fsz = (int)(fl->type ? fl->type->size : 8);
|
||||
ftype = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cgexpr(c, f->lhs, locals); /* AX or (AX,BX) or X0 */
|
||||
int f_isf32 = 0;
|
||||
if (fld_isfloat(ftype, &f_isf32)) {
|
||||
int mov = f_isf32 ? A_MOVSS : A_MOVSD;
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BX, (int)foff));
|
||||
continue;
|
||||
}
|
||||
/* str IS []u8: cgexpr leaves (AX=ptr, BX=len,
|
||||
* CX=cap). Route the heap base through DX so all
|
||||
* three survive — CX now holds cap, BX holds len
|
||||
* (#1/Phase 3). */
|
||||
Type *fu = type_chase_named(ftype);
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_DX, (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_DX, (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, (int)foff + 16));
|
||||
continue;
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
int op = A_MOVQ;
|
||||
if (fsz == 1) op = A_MOVB;
|
||||
else if (fsz == 4) op = A_MOVL;
|
||||
ins2(c, op, areg(D_AX), amem(D_BX, (int)foff));
|
||||
}
|
||||
/* C7c: route the heap field-fill through the shared
|
||||
* structlit helper in DST_PTR_SP mode — base reloaded
|
||||
* from the just-pushed heap ptr at (SP). The prior
|
||||
* inline loop had only float/str/scalar arms, so a
|
||||
* nested struct/array/tuple field VALUE (an inner
|
||||
* N_STRUCTLIT / N_ARRLIT) fell to the scalar tail and
|
||||
* stored AX=0 over the whole inner slot, dropping its
|
||||
* leaves. The helper recurses to arbitrary depth and
|
||||
* reuses the tagged/call/str/slice/array/agg arms,
|
||||
* closing the class symmetrically with the BP-rel and
|
||||
* global structlit sites. Byte-identical to the old
|
||||
* inline loop for the float/str/scalar fields the
|
||||
* corpus actually allocs (same (SP) reload, disp=0). */
|
||||
cg_structlit_fill(c, &locals, u, v, DST_PTR_SP,
|
||||
0, NULL, 0);
|
||||
} else {
|
||||
cgexpr(c, v, locals); /* AX = value */
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
|
||||
@@ -5911,60 +5911,19 @@ fn cgalloc(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (v.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
if (si != nil) {
|
||||
let f: *syntax.node = v.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_FIELD) {
|
||||
let fname: str = f.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (syntax.streq(fn_, fname)) {
|
||||
cgexpr(c, f.lhs);
|
||||
// alloc(T{ fval = v }) for f64/f32 field: cgexpr left
|
||||
// the value in X0, not AX — route the store via MOVSD/MOVSS.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
fi = nil;
|
||||
} else { if (isstrtype(c, fi.tnode)) {
|
||||
// str IS []u8: cgexpr leaves (AX=ptr,
|
||||
// BX=len, CX=cap). Route the heap base
|
||||
// through DX so all three survive — CX
|
||||
// holds cap, BX holds len (#1/Phase 3).
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitdispreg((fi.foff + 16): i64, "DX");
|
||||
emitline("\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
fi = nil;
|
||||
};};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
// C7c: route the heap field-fill through the shared
|
||||
// structlit helper in mode 3 (DST_PTR_SP) — base reloaded
|
||||
// from the just-pushed heap ptr at (SP). The prior inline
|
||||
// loop had only float/str/scalar arms, so a nested
|
||||
// struct/array/tuple field VALUE (an inner N_STRUCTLIT /
|
||||
// N_ARRLIT) fell to the scalar tail and stored AX=0 over
|
||||
// the whole inner slot, dropping its leaves. The helper
|
||||
// recurses to arbitrary depth and reuses the
|
||||
// tagged/call/str/slice/array/agg arms, closing the class
|
||||
// symmetrically with cstage's DST_PTR_SP arm. Byte-id to
|
||||
// the old inline loop for the float/str/scalar fields the
|
||||
// corpus actually allocs (same (SP) reload, disp=0).
|
||||
cgstructlitfill(c, si, v, 3, 0, "", 0);
|
||||
};
|
||||
} else {
|
||||
// #57 (retained cs!=ww, deferred): scalar/ptr alloc keeps the
|
||||
|
||||
@@ -5076,6 +5076,15 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node,
|
||||
// 2 = DST_GLOBAL — base = BX, reloaded via `LEAQ srcname(SB),
|
||||
// BX` with the same cadence as DST_PTR_LOCAL.
|
||||
// srcoff unused.
|
||||
// 3 = DST_PTR_SP — base = BX, reloaded via `MOVQ (SP), BX` with
|
||||
// the same cadence as DST_PTR_LOCAL. dst is the
|
||||
// alloc-heap base saved by cgalloc's `PUSHQ AX`
|
||||
// (top-of-stack); cgexpr is stack-balanced so
|
||||
// (SP) keeps pointing at it across the walk.
|
||||
// srcoff/srcname unused. C7c: nested struct/
|
||||
// array/tuple field VALUE in alloc(Outer{x =
|
||||
// Inner{..}}) now writes the inner leaves
|
||||
// instead of storing AX=0 over the inner slot.
|
||||
//
|
||||
// Param semantics (locked in here so the recursion contract is
|
||||
// clear):
|
||||
@@ -5139,6 +5148,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= totsize) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -5198,6 +5210,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo,
|
||||
fieldnode.lhs, basereg,
|
||||
disp + fi.foff, fi.fsz);
|
||||
@@ -5293,6 +5308,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
let full: i32 = cfsz / 8;
|
||||
let ci: i32 = 0;
|
||||
for (ci < full) {
|
||||
@@ -5370,11 +5388,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), DX\n");
|
||||
} else { if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), DX\n");
|
||||
};
|
||||
}; };
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((disp + fi.foff): i64, "DX");
|
||||
emitline("\n");
|
||||
@@ -5464,6 +5484,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
let eoff: i32 = disp + fi.foff + idx * esz;
|
||||
if (isfloatel) {
|
||||
emitline("\t");
|
||||
@@ -5503,6 +5526,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
let eoff: i32 = disp + fi.foff + idx * esz;
|
||||
if (isfloatel) {
|
||||
emitline("\t");
|
||||
@@ -5556,11 +5582,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else { if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
}; };
|
||||
if (disp + fi.foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint((disp + fi.foff): i64);
|
||||
@@ -5589,6 +5617,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
|
||||
77
test/lang/alloc_nested_field_test.ww
Normal file
77
test/lang/alloc_nested_field_test.ww
Normal file
@@ -0,0 +1,77 @@
|
||||
// alloc_nested_field_test — a nested aggregate field VALUE inside an
|
||||
// alloc(struct-literal) heap init must survive (C7c, task #6). The
|
||||
// alloc(value) field-fill loop had only float/str/scalar arms; a field
|
||||
// whose value is itself an N_STRUCTLIT / N_ARRLIT fell to the scalar
|
||||
// tail, so cgexpr(inner-literal) left AX=0 and the loop stored $0 over
|
||||
// the whole inner slot — silently dropping every inner leaf (byte-id-
|
||||
// gate-blind: both stages emitted the same wrong fill). The fix routes
|
||||
// the heap fill through the shared cg_structlit_fill / cgstructlitfill
|
||||
// helper in DST_PTR_SP mode (base reloaded from the pushed heap ptr at
|
||||
// (SP)), which recurses to arbitrary depth.
|
||||
//
|
||||
// Every row reads the heap struct back via `let o: T = *p` (a whole-
|
||||
// struct deref-load to a BP-rel local) so the assertions never traverse
|
||||
// the chained-dot-through-heap-ptr read path (a separate pre-existing
|
||||
// cs!=ww the C7c fill fix does not touch). Each row asserts the nested
|
||||
// leaf VALUE (reddens when the fix is reverted: the dropped leaf reads
|
||||
// 0) AND the sibling scalar leaves before/after it (no clobber).
|
||||
|
||||
package alloc_nested_field_test;
|
||||
|
||||
type Inner = struct { q: i64 };
|
||||
type Outer = struct { x: Inner, y: i64 };
|
||||
|
||||
type Mid = struct { lo: i64, inner: Inner, hi: i64 };
|
||||
type Nest = struct { a: i64, mid: Mid, b: i64 };
|
||||
|
||||
type WithArr = struct { lead: i64, arr: [3]i64, trail: i64 };
|
||||
|
||||
type TwoNest = struct { head: i64, s: Inner, arr: [2]i64, tail: i64 };
|
||||
|
||||
@test fn depth1_nested_struct() void = {
|
||||
// Inner{q=10} is the field VALUE of x; pre-fix x.q dropped to 0.
|
||||
let p = alloc(Outer{ x = Inner{ q = 10 }, y = 5 })!;
|
||||
let o: Outer = *p;
|
||||
assert(o.x.q == 10); // the dropped leaf
|
||||
assert(o.y == 5); // trailing sibling, must not clobber
|
||||
};
|
||||
|
||||
@test fn depth2_nested_struct() void = {
|
||||
// Outer{ mid = Mid{ inner = Inner{q} } } — two levels of nesting.
|
||||
// Every inner leaf lands at base + accumulated offset.
|
||||
let p = alloc(Nest{ a = 1, mid = Mid{ lo = 2, inner = Inner{ q = 3 }, hi = 4 }, b = 5 })!;
|
||||
let o: Nest = *p;
|
||||
assert(o.a == 1); // lead sibling
|
||||
assert(o.mid.lo == 2); // inner lead sibling
|
||||
assert(o.mid.inner.q == 3); // depth-2 dropped leaf
|
||||
assert(o.mid.hi == 4); // inner trail sibling
|
||||
assert(o.b == 5); // trail sibling
|
||||
};
|
||||
|
||||
@test fn nested_array_field() void = {
|
||||
// arr = [10,20,30] is an N_ARRLIT field VALUE; pre-fix every
|
||||
// element dropped (scalar tail stored word0 only). lead/trail
|
||||
// bracket the array to prove the element stores stay in bounds.
|
||||
let p = alloc(WithArr{ lead = 7, arr = [10, 20, 30], trail = 9 })!;
|
||||
let o: WithArr = *p;
|
||||
assert(o.lead == 7); // lead sibling
|
||||
assert(o.arr[0] == 10); // dropped elements
|
||||
assert(o.arr[1] == 20);
|
||||
assert(o.arr[2] == 30);
|
||||
assert(o.trail == 9); // trail sibling
|
||||
};
|
||||
|
||||
@test fn multi_nested_fields() void = {
|
||||
// Two ADJACENT nested-aggregate fields (nested STRUCT s, then nested
|
||||
// ARRAY arr) bracketed by scalar siblings — proves the heap-base
|
||||
// reload after a full nested recursion serves the NEXT nested field's
|
||||
// recursion, not only a trailing scalar (every other row follows a
|
||||
// nested field with a scalar). pre-fix both s.q and the arr elems drop.
|
||||
let p = alloc(TwoNest{ head = 1, s = Inner{ q = 2 }, arr = [3, 4], tail = 5 })!;
|
||||
let o: TwoNest = *p;
|
||||
assert(o.head == 1); // lead sibling
|
||||
assert(o.s.q == 2); // nested-struct leaf
|
||||
assert(o.arr[0] == 3); // nested-array elems, after a nested struct
|
||||
assert(o.arr[1] == 4);
|
||||
assert(o.tail == 5); // trail sibling
|
||||
};
|
||||
Reference in New Issue
Block a user