cgen: convert the struct-store loop arms to a tinfo-native fill (#31 c2)
The W1/W2/W5/W4b store / structlit-fill arms still resolved struct field LAYOUT by bare-leaf name (structlookupchain / structlookup / letvarstructinfo) inside the per-field store loop -- the write-side twin of commit-1's read fix. Under a cross-module same-leaf collision (two modules each exporting a `pair`, 16B vs 24B) the field is stored at the wrong offset/width; an interior 2-byte field store silently corrupted neighbours. cstage walks the resolved Type* (never a name) and is correct; this aligns wwstage UP (ww-only change). Convert each store loop to a tinfo-native fill. Two new helpers in cgenutil.ww: sretretsizetn (a verbatim port of cstage cg_sret_retsize -- 4 kind-branches, no TY_BANG, dropping the #129 module-juggling that was pure name-keying compensation, redundant once the lookup walks resolved tinfo) and cgstructlitfilltn (the tinfo-native twin of cgstructlitfill, recursing on tichase(tf.type_).fields with NO name lookup at any nesting level). Supporting twins storeopsz / tfieldlookup / cgdotfieldhardstoptn. Because the fill never bottoms out on a name, the W4b "decisive wall" (a structlit that needed a (sname,smod)-keyed structinfo the stamped leaf-only tinfo couldn't supply) dissolves by construction, with no module-aware tinfo->structinfo bridge. This CLOSES the nine in-loop nested sub-arms (N_CALL / N_STRUCTLIT / N_IDENT field receive) by construction -- the tf walk has no name lookup. The non-reddenable global arms (W5/W4b) are converted-for-construction: a qualified global decl resolves its bare leaf correctly today, so byte-id (cs.s==ww.s) is their net, not a value pin. `!T` is kind-preserving in both stages (resolve_type / tinfofornode), so a `!struct` sret sizes as the inner struct, never TAGGED -- the #129 drop changes no size. Completes the 2-commit #31 arc (RULING R2 / Opt-2, .ai/ken-31-spec.md): the #224 name-keyed receiver-layout cluster is now closed by construction. The residual nested-field surface (the shared cgstructlitfill kept for its non-#31 callers + the cgenstmt/cgenutil let-receive/fill family) is tracked as #32. Pins: test/wcc/797 value-asserts ptr-WRITE (W1) and val-WRITE (W2), each reddening under independent per-arm revert; nestfill_box drives cgstructlitfilltn's nested recursion (bootstrap-uncovered -- the corpus has no struct-literal field store -- so the value pin is its only net).
This commit is contained in:
@@ -350,6 +350,34 @@ fn cgdotfieldhardstop(c: *cgen, ftn: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// cgdotfieldhardstoptn — tinfo twin of cgdotfieldhardstop (#31): the
|
||||
// single-dot field compound combine speaks integer ABI only, so a
|
||||
// tagged/str/slice/float field compound loud-stops. Keyed off the
|
||||
// checker-STAMPED field tinfo (the receiver-layout tf walk), not a
|
||||
// tnode. Messages verbatim from cgdotfieldhardstop. (#34/rule-7)
|
||||
fn cgdotfieldhardstoptn(c: *cgen, ft: *syntax.tinfo) void = {
|
||||
if (syntax.typeistagged(ft)) {
|
||||
let m: str = "single-dot field compound on tagged field not wired (#34/rule-7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (syntax.typeisstr(ft)) {
|
||||
let m: str = "single-dot field compound on str field not wired (#34/rule-7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (syntax.typeisslice(ft)) {
|
||||
let m: str = "single-dot field compound on slice field not wired (#34/rule-7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (syntax.typeisfloat(ft)) {
|
||||
let m: str = "single-dot field compound on float field not wired (#34/rule-7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
|
||||
fn cgtryunwcursor(c: *cgen, n: *syntax.node, opname: str) void = {
|
||||
let u: *syntax.tinfo = nil;
|
||||
if (n.lhs != nil) { u = n.lhs.type_: *syntax.tinfo; };
|
||||
@@ -10592,34 +10620,39 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (tn != nil) { lkind = tn.kind; };
|
||||
// Pointer-to-struct: deref then store.
|
||||
if (lkind == syntax.nkind.N_TPTR) {
|
||||
let inner: *syntax.node = tn.lhs;
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (inner != nil) {
|
||||
if (inner.kind == syntax.nkind.N_TNAME) { sname = inner.str; };
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
// structlookupchain (#22) handles the
|
||||
// alias-chain miss; same shape as the
|
||||
// cgdot pointer-to-struct read site.
|
||||
let si: *structinfo = structlookupchain(c, inner);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (syntax.streq(fn_, fld)) {
|
||||
// #31: resolve the *struct field OFFSET + type off the
|
||||
// checker-STAMPED receiver tinfo (tichase(base.type_)->.sub),
|
||||
// NOT the name-keyed leaf lookup. A cross-module same-leaf
|
||||
// collision makes the bare leaf mis-resolve to a FOREIGN
|
||||
// same-leaf struct -> the field is STORED at the wrong
|
||||
// offset/width (the inferred-local ptr-WRITE row). The
|
||||
// stamped tinfo carries the right layout; mirror cstage
|
||||
// type_chase_named(bu->sub)->fields. The in-loop #32 nested
|
||||
// struct-receive/structlit/ident sub-arms close by
|
||||
// construction (the tf walk has no name lookup). Non-struct
|
||||
// pointees fall through to the str/slice arm.
|
||||
let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
|
||||
if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); };
|
||||
if (sti != nil) {
|
||||
if (sti.kind == syntax.tykind.TY_STRUCT) {
|
||||
let tf: *syntax.tfield = sti.fields;
|
||||
for (tf != nil) {
|
||||
if (syntax.streq(tf.name, fld)) {
|
||||
let foff: i32 = tf.offset: i32;
|
||||
let ftraw: *syntax.tinfo = tf.type_;
|
||||
let fu: *syntax.tinfo = tichase(ftraw);
|
||||
// Tagged-union field via *struct base — full slot
|
||||
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
|
||||
// fell through to the scalar store and dropped tag
|
||||
// + payload.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
&& syntax.typeistagged(ftraw)) {
|
||||
let fsz: i32 = fu.size: i32;
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo,
|
||||
n.rhs, "BX", fi.foff, fsz);
|
||||
cgwidentaggedstore(c, ftraw,
|
||||
n.rhs, "BX", foff, fsz);
|
||||
return;
|
||||
};
|
||||
// #234-tail: via-ptr (`p.f`) sret field STORE. The dest must
|
||||
@@ -10629,7 +10662,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL
|
||||
&& sretretsize(c, fi.tnode) > 0) {
|
||||
&& sretretsizetn(c, ftraw) > 0) {
|
||||
let m234: str = "#234-tail: over-cap tuple sret store to via-ptr field dest unsupported\n";
|
||||
os.write(2, m234.ptr, m234.len: u64);
|
||||
os.exit(1);
|
||||
@@ -10654,20 +10687,16 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
&& n.rhs != nil
|
||||
&& (n.rhs.kind == syntax.nkind.N_CALL
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structabisize(ssi);
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
let ssz: i32 = fu.size: i32;
|
||||
if (ssz <= 24) {
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgaggregstore(c, "BX", fi.foff, ssz, false);
|
||||
cgaggregstore(c, "BX", foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -10679,26 +10708,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// field store.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
|
||||
fi.foff);
|
||||
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
cgstructlitfilltn(c, fu, n.rhs, 1, lc.off, "",
|
||||
foff);
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_IDENT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
&& n.rhs.kind == syntax.nkind.N_IDENT) {
|
||||
let srhs: *local = localfindnode(c, n.rhs.str);
|
||||
if (ssi != nil) { if (srhs != nil) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
@@ -10709,7 +10730,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 8;
|
||||
};
|
||||
@@ -10718,7 +10739,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 4;
|
||||
};
|
||||
@@ -10727,7 +10748,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 2;
|
||||
};
|
||||
@@ -10736,7 +10757,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
@@ -10748,11 +10769,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
};
|
||||
@@ -10762,9 +10783,9 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// PLUSEQ is commutative; MINUSEQ
|
||||
// needs lhs - rhs (BX is old lhs,
|
||||
// AX is rhs).
|
||||
cgdotfieldhardstop(c, fi.tnode);
|
||||
cgdotfieldhardstoptn(c, ftraw);
|
||||
let uns34: bool = false;
|
||||
if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { uns34 = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; };
|
||||
uns34 = syntax.typeisunsigned(ftraw);
|
||||
cgdotfieldcombine(c, n.op, uns34);
|
||||
};
|
||||
if (n.op == syntax.tkind.TK_ASSIGN) {
|
||||
@@ -10772,33 +10793,33 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// store the full 3-word {ptr,len,cap} from (AX,BX,CX).
|
||||
// CX holds cap, so stage the struct addr in DX and
|
||||
// store at foff/+8/+16 (#1/Phase 3).
|
||||
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "DX");
|
||||
emitdispreg(foff: i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "DX");
|
||||
emitdispreg((foff + 8): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitdispreg((fi.foff + 16): i64, "DX");
|
||||
emitdispreg((foff + 16): i64, "DX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 plain `=` via *struct: cgexpr left the
|
||||
// value in X0. Reload struct ptr and MOVSD/MOVSS.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (syntax.typeisfloat(ftraw)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
@@ -10806,38 +10827,48 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = storeopsz(fu.size: i32);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Direct struct local: store at off+foff.
|
||||
if (lkind == syntax.nkind.N_TNAME) {
|
||||
// structlookupchain (#22) — same shape
|
||||
// as the cgdot direct-local read site.
|
||||
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)) {
|
||||
// #31: resolve the value-struct field OFFSET + type off the
|
||||
// checker-STAMPED receiver tinfo (tichase(base.type_)), NOT
|
||||
// the name-keyed leaf lookup. A cross-module same-leaf
|
||||
// collision makes the bare leaf mis-resolve to a FOREIGN
|
||||
// same-leaf struct -> the field is STORED at the wrong
|
||||
// offset/width (the inferred-local val-WRITE row, ww=107).
|
||||
// The stamped tinfo carries the right layout; mirror cstage
|
||||
// type_chase_named(base->type)->fields. The in-loop #32
|
||||
// nested struct-receive/structlit/ident sub-arms close by
|
||||
// construction (the tf walk has no name lookup).
|
||||
let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
|
||||
if (sti != nil && sti.kind == syntax.tykind.TY_STRUCT) {
|
||||
let tf: *syntax.tfield = sti.fields;
|
||||
for (tf != nil) {
|
||||
if (syntax.streq(tf.name, fld)) {
|
||||
let foff: i32 = tf.offset: i32;
|
||||
let ftraw: *syntax.tinfo = tf.type_;
|
||||
let fu: *syntax.tinfo = tichase(ftraw);
|
||||
// Tagged-union field in a direct struct local —
|
||||
// full slot rewrite at (lc.off + fi.foff)(BP)
|
||||
// full slot rewrite at (lc.off + foff)(BP)
|
||||
// via cgwidentaggedstore basereg="BP". Pre-#26
|
||||
// fell through and dropped tag + payload.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo,
|
||||
n.rhs, "BP", lc.off + fi.foff, fsz);
|
||||
&& syntax.typeistagged(ftraw)) {
|
||||
let fsz: i32 = fu.size: i32;
|
||||
cgwidentaggedstore(c, ftraw,
|
||||
n.rhs, "BP", lc.off + foff, fsz);
|
||||
return;
|
||||
};
|
||||
// #234: over-cap sret STORE into a LOCAL struct field —
|
||||
@@ -10845,14 +10876,14 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// (sretretsize > 0: a >24B struct OR an over-cap tuple).
|
||||
// STORE-twin of the Fold-B sret RECEIVE (a937d67): point
|
||||
// the callee's hidden RDI dest at the field slot
|
||||
// (c.sretdestoff = lc.off + fi.foff) so it writes the
|
||||
// (c.sretdestoff = lc.off + foff) so it writes the
|
||||
// WHOLE value there, never the truncating generic store
|
||||
// below. Mirror of cstage cgen.c (#234) field local arm.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL
|
||||
&& sretretsize(c, fi.tnode) > 0) {
|
||||
c.sretdestoff = lc.off + fi.foff;
|
||||
&& sretretsizetn(c, ftraw) > 0) {
|
||||
c.sretdestoff = lc.off + foff;
|
||||
cgexpr(c, n.rhs);
|
||||
c.sretdestoff = 0;
|
||||
return;
|
||||
@@ -10862,9 +10893,9 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// added with #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; sized stores
|
||||
// directly at (lc.off+fi.foff)(BP).
|
||||
// directly at (lc.off+foff)(BP).
|
||||
// N_STRUCTLIT: field-walk; each inner
|
||||
// field stored at +fi.foff+inner_foff(BP).
|
||||
// field stored at +foff+inner_foff(BP).
|
||||
// BP-rel direct, no addr scratch needed. #12: an
|
||||
// unwrap `b.f = mk()!` rides the same {AX,DX,CX}
|
||||
// shape (producer shift) — admit it alongside
|
||||
@@ -10873,17 +10904,13 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
&& n.rhs != nil
|
||||
&& (n.rhs.kind == syntax.nkind.N_CALL
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structabisize(ssi);
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
let ssz: i32 = fu.size: i32;
|
||||
if (ssz <= 24) {
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", lc.off + fi.foff, ssz, false);
|
||||
cgaggregstore(c, "BP", lc.off + foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -10894,26 +10921,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// no BX reload.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
|
||||
lc.off + fi.foff);
|
||||
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
cgstructlitfilltn(c, fu, n.rhs, 0, 0, "",
|
||||
lc.off + foff);
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_IDENT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
&& n.rhs.kind == syntax.nkind.N_IDENT) {
|
||||
let srhs: *local = localfindnode(c, n.rhs.str);
|
||||
if (ssi != nil) { if (srhs != nil) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) {
|
||||
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= ssz) {
|
||||
@@ -10921,7 +10940,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((lc.off + fi.foff + k): i64);
|
||||
emitoff((lc.off + foff + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 8;
|
||||
};
|
||||
@@ -10930,7 +10949,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((lc.off + fi.foff + k): i64);
|
||||
emitoff((lc.off + foff + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 4;
|
||||
};
|
||||
@@ -10939,7 +10958,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff((lc.off + fi.foff + k): i64);
|
||||
emitoff((lc.off + foff + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 2;
|
||||
};
|
||||
@@ -10948,7 +10967,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((lc.off + fi.foff + k): i64);
|
||||
emitoff((lc.off + foff + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
@@ -10960,11 +10979,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// scalar field: load current → push
|
||||
// → eval rhs → combine → store
|
||||
// (mirror cgen.c:3477 local arm).
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
};
|
||||
@@ -10973,47 +10992,47 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
// PLUSEQ commutes; MINUSEQ needs
|
||||
// lhs-rhs (BX old lhs, AX rhs).
|
||||
cgdotfieldhardstop(c, fi.tnode);
|
||||
cgdotfieldhardstoptn(c, ftraw);
|
||||
let uns34: bool = false;
|
||||
if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { uns34 = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; };
|
||||
uns34 = syntax.typeisunsigned(ftraw);
|
||||
cgdotfieldcombine(c, n.op, uns34);
|
||||
};
|
||||
// str/slice field direct: str IS []u8, so both store the
|
||||
// full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16.
|
||||
// BP base, no scratch reload needed; the generic fldstoreop
|
||||
// below would write only AX, dropping .len/.cap (#1/Phase 3).
|
||||
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((lc.off + fi.foff + 8): i64);
|
||||
emitoff((lc.off + foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((lc.off + fi.foff + 16): i64);
|
||||
emitoff((lc.off + foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 direct struct local store: route via X0.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (syntax.typeisfloat(ftraw)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = storeopsz(fu.size: i32);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((lc.off + fi.foff): i64);
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -11108,11 +11127,22 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (base.kind == syntax.nkind.N_IDENT) {
|
||||
let bn: str = base.str;
|
||||
if (localfindnode(c, bn) == nil) {
|
||||
let si: *structinfo = letvarstructinfo(c, bn);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (syntax.streq(fi.fname, fld)) {
|
||||
// #31: resolve the global value-struct field OFFSET + type
|
||||
// off the checker-STAMPED receiver tinfo (tichase(base.type_)),
|
||||
// NOT the name-keyed global-struct leaf lookup. A global decl
|
||||
// is qualified -> non-reddenable; converted for close-by-
|
||||
// construction (byte-id). Mirror cstage type-keyed N_DOT global
|
||||
// arm. The in-loop #32 nested struct-receive/structlit/ident
|
||||
// sub-arms close by construction (the tf walk has no name
|
||||
// lookup).
|
||||
let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
|
||||
if (sti != nil && sti.kind == syntax.tykind.TY_STRUCT) {
|
||||
let tf: *syntax.tfield = sti.fields;
|
||||
for (tf != nil) {
|
||||
if (syntax.streq(tf.name, fld)) {
|
||||
let foff: i32 = tf.offset: i32;
|
||||
let ftraw: *syntax.tinfo = tf.type_;
|
||||
let fu: *syntax.tinfo = tichase(ftraw);
|
||||
// #234-tail: GLOBAL (`g.f`) sret field STORE. c.sretdestoff is
|
||||
// BP-relative only and can't name a global slot; the runtime
|
||||
// RDI-pointer dest variant is deferred. HARD-STOP loud, never
|
||||
@@ -11120,7 +11150,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL
|
||||
&& sretretsize(c, fi.tnode) > 0) {
|
||||
&& sretretsizetn(c, ftraw) > 0) {
|
||||
let m234: str = "#234-tail: over-cap tuple sret store to global field dest unsupported\n";
|
||||
os.write(2, m234.ptr, m234.len: u64);
|
||||
os.exit(1);
|
||||
@@ -11134,20 +11164,16 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// N_STRUCTLIT: field-walk; reload BX per store.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structabisize(ssi);
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
let ssz: i32 = fu.size: i32;
|
||||
if (ssz <= 24) {
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
cgaggregstore(c, "BX", fi.foff, ssz, false);
|
||||
cgaggregstore(c, "BX", foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -11159,26 +11185,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// field store.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
|
||||
fi.foff);
|
||||
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
cgstructlitfilltn(c, fu, n.rhs, 2, 0, bn,
|
||||
foff);
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_IDENT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
&& n.rhs.kind == syntax.nkind.N_IDENT) {
|
||||
let srhs: *local = localfindnode(c, n.rhs.str);
|
||||
if (ssi != nil) { if (srhs != nil) {
|
||||
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
@@ -11189,7 +11207,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 8;
|
||||
};
|
||||
@@ -11198,7 +11216,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 4;
|
||||
};
|
||||
@@ -11207,7 +11225,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 2;
|
||||
};
|
||||
@@ -11216,7 +11234,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitoff((srhs.off + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + k): i64, "BX");
|
||||
emitdispreg((foff + k): i64, "BX");
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
@@ -11230,19 +11248,19 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// below truncates to 1 word, silently dropping tag
|
||||
// and payload.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
&& syntax.typeistagged(ftraw)) {
|
||||
let fsz: i32 = fu.size: i32;
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
cgwidentaggedstore(c,
|
||||
fi.tnode.type_: *syntax.tinfo,
|
||||
n.rhs, "BX", fi.foff, fsz);
|
||||
ftraw,
|
||||
n.rhs, "BX", foff, fsz);
|
||||
return;
|
||||
};
|
||||
if (n.op == syntax.tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
|
||||
// str OR slice field: str IS []u8, so
|
||||
// both store the full {ptr,len,cap}
|
||||
// header (cstage cgen.c:5055 gates
|
||||
@@ -11256,39 +11274,39 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), DX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "DX");
|
||||
emitdispreg(foff: i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "DX");
|
||||
emitdispreg((foff + 8): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitdispreg((fi.foff + 16): i64, "DX");
|
||||
emitdispreg((foff + 16): i64, "DX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// f64/f32 plain `=` on global struct field: value is
|
||||
// in X0; LEAQ the base into BX and MOVSD/MOVSS.
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
if (syntax.typeisfloat(ftraw)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = storeopsz(fu.size: i32);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
@@ -11296,34 +11314,34 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// → push → eval rhs → combine →
|
||||
// store. cgexpr clobbers BX, so
|
||||
// re-LEAQ for the store.
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline(", BX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
cgdotfieldhardstop(c, fi.tnode);
|
||||
cgdotfieldhardstoptn(c, ftraw);
|
||||
let uns34: bool = false;
|
||||
if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { uns34 = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; };
|
||||
uns34 = syntax.typeisunsigned(ftraw);
|
||||
cgdotfieldcombine(c, n.op, uns34);
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
let sop: str = storeopsz(fu.size: i32);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitdispreg(foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -12305,18 +12323,20 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
|| gau.kind == syntax.tykind.TY_ARRAY
|
||||
|| gau.kind == syntax.tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
let gsi49: *structinfo = nil;
|
||||
if (lvftn.kind == syntax.nkind.N_TNAME) {
|
||||
gsi49 = structlookup(c, lvftn.str);
|
||||
};
|
||||
if (gsi49 == nil) {
|
||||
// #31: fill off the stamped struct tinfo (gau =
|
||||
// tichase(lvftn.type_)), NOT structlookup(lvftn.str). A
|
||||
// global decl is qualified -> non-reddenable; converted
|
||||
// for close-by-construction (byte-id). The W4b nested
|
||||
// in-loop structlookup sub-arms close by construction
|
||||
// (cgstructlitfilltn recurses on tichase(tf.type_)).
|
||||
if (gau.kind != syntax.tykind.TY_STRUCT) {
|
||||
// wwstage-only bail (anonymous
|
||||
// type; the @placescr precedent).
|
||||
let m49d: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49d.ptr, m49d.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgstructlitfill(c, gsi49, n.rhs, 2, 0, nm, 0);
|
||||
cgstructlitfilltn(c, gau, n.rhs, 2, 0, nm, 0);
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) {
|
||||
|
||||
@@ -1695,6 +1695,19 @@ fn loadopsz(sigd: bool, sz: i32) str = {
|
||||
return "MOVQ";
|
||||
};
|
||||
|
||||
// storeopsz — store op when the size has already been resolved
|
||||
// upstream off a stamped tinfo (the #31 receiver-layout store choke-
|
||||
// point). Size-only twin of fieldstoreop's body {1->MOVB, 2->MOVW,
|
||||
// 4->MOVL, else MOVQ}; the store side carries no signedness. Preserves
|
||||
// the #15 sub-8 guard (a 2B tail field stores MOVW, not a slot-wide
|
||||
// MOVQ into saved BP).
|
||||
fn storeopsz(sz: i32) str = {
|
||||
if (sz == 1) { return "MOVB"; };
|
||||
if (sz == 2) { return "MOVW"; };
|
||||
if (sz == 4) { return "MOVL"; };
|
||||
return "MOVQ";
|
||||
};
|
||||
|
||||
// localloadop — read instruction for a scalar local/let load. Same
|
||||
// dispatch as fieldloadop, but keyed on the value's own tnode.type_.
|
||||
// Lets the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot
|
||||
@@ -2214,6 +2227,80 @@ export fn sretretsize(c: *cgen, t: *syntax.node) i32 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// tfieldlookup — the named field `fld` in struct tinfo `sti`'s stamped
|
||||
// field chain, else nil. The #31 receiver-layout store choke-point: the
|
||||
// tfield twin of the name-keyed `for (fi != nil)` fieldinfo walk. A
|
||||
// cross-module same-leaf collision that mis-resolved structlookupchain
|
||||
// cannot reach a stamped tinfo, so every field-OFFSET/width/kind keyed
|
||||
// off the returned tf is collision-immune.
|
||||
fn tfieldlookup(sti: *syntax.tinfo, fld: str) *syntax.tfield = {
|
||||
if (sti == nil) { return nil; };
|
||||
let tf: *syntax.tfield = sti.fields;
|
||||
for (tf != nil) {
|
||||
if (syntax.streq(tf.name, fld)) { return tf; };
|
||||
tf = tf.tnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// sretretsizetn — verbatim port of cstage cg_sret_retsize (cmd/w6c/
|
||||
// cgen.c:380) keyed on the checker-STAMPED *tinfo. The tinfo twin of
|
||||
// sretretsize, the #31 store-loop choke-point (the sret hard-stop /
|
||||
// sret-receive field sub-arms). Walks resolved tinfo, so the #129
|
||||
// aliassamemod/module-juggling sretretsize carries — pure name-keying
|
||||
// compensation for structlookup's any-module fallback — is NOT needed
|
||||
// and absent: tichase(t) peels NAMED then falls through all four kind
|
||||
// tests to 0, exactly what #129 reconstructed by name (cstage sees a
|
||||
// resolved Type*, never a name). There is NO TY_BANG tinfo kind — `!T`
|
||||
// folds into TY_TAGGED's per-variant iserror (typ.ww:82-90), so there
|
||||
// is no error-peel branch; mirror cstage's four branches exactly. `c`
|
||||
// kept for call-site symmetry with sretretsize (unused: tinfo carries
|
||||
// the module-independent layout).
|
||||
fn sretretsizetn(c: *cgen, t: *syntax.tinfo) i32 = {
|
||||
let u: *syntax.tinfo = tichase(t);
|
||||
if (u == nil) { return 0; };
|
||||
if (u.kind == syntax.tykind.TY_STRUCT) {
|
||||
if (u.size: i32 <= 24) { return 0; };
|
||||
return u.size: i32;
|
||||
};
|
||||
// #38: a tagged union rides AX(tag)+DX/CX/R8 = TUPLE_GPCAP
|
||||
// eightbytes; nullable folds to one ptr word. Mirrors cstage
|
||||
// cg_sret_retsize TY_TAGGED arm.
|
||||
if (u.kind == syntax.tykind.TY_TAGGED) {
|
||||
if (u.nullable != 0) { return 0; };
|
||||
if (u.size: i32 <= TUPLE_GPCAP * 8) { return 0; };
|
||||
return u.size: i32;
|
||||
};
|
||||
// #267: arrays ride the struct-return ABI (≤24 reg / >24 sret).
|
||||
if (u.kind == syntax.tykind.TY_ARRAY) {
|
||||
if (u.size: i32 <= 24) { return 0; };
|
||||
return u.size: i32;
|
||||
};
|
||||
// #10: over-cap tuple → sret. Walk the positional element chain
|
||||
// over the SAME caps the SEND/receive use (a float = 1 SSE
|
||||
// eightbyte; str/slice/tagged ride their box-rounded GP stride via
|
||||
// tupeslot; a scalar 1 GP word). Mirrors cstage cg_sret_retsize
|
||||
// TY_TUPLE arm; tupleelems is the tinfo positional chain.
|
||||
if (u.kind == syntax.tykind.TY_TUPLE) {
|
||||
let gptotal: i32 = 0;
|
||||
let ssecount: i32 = 0;
|
||||
let te: *syntax.ttupleelem = u.tupleelems;
|
||||
for (te != nil) {
|
||||
if (syntax.typeisfloat(te.type_)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
gptotal = gptotal + tupeslot(te.type_) / 8;
|
||||
};
|
||||
te = te.tnext;
|
||||
};
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP) {
|
||||
return u.size: i32;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// callsretsize — if N_CALL `n`'s callee returns a plain TY_STRUCT
|
||||
// > 24B, return its natural size; else 0. Wraps sretretsize over the
|
||||
// callee's resolved return type, used by cglet / cgassign receive
|
||||
@@ -5738,3 +5825,419 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *syntax.node, bpoff: i32) v
|
||||
if (si == nil) { return; };
|
||||
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
|
||||
};
|
||||
|
||||
// cgstructlitfilltn — tinfo-native twin of cgstructlitfill, the #31
|
||||
// receiver-layout fill choke-point. Walks the struct's checker-STAMPED
|
||||
// tfield chain (lu.fields) and keys every per-field decision off the
|
||||
// resolved tf.type_ (kind / size / signedness) — NO structlookup, NO
|
||||
// fi.tnode names. Nested struct fields recurse on tichase(tf.type_), so
|
||||
// a cross-module same-leaf collision (the #224 cluster) cannot reach a
|
||||
// name lookup at any nesting level. Faithful mirror of cstage
|
||||
// cg_structlit_fill, which recurses on type_chase_named(fl->type)
|
||||
// (cgen.c:3179) over resolved Type*. Emission (mode reloads, register
|
||||
// staging, store ordering) is byte-identical to cgstructlitfill; only
|
||||
// the LAYOUT source flips from si/fieldinfo to lu/tfield. tinfo.size
|
||||
// equals structabisize/fi.fsz for a struct field (check.ww:2468 ABI
|
||||
// formula; NAMED.size==under.size at :2234), so the size-keyed stores
|
||||
// match. Modes: 0=DST_BP, 1=DST_PTR_LOCAL(srcoff), 2=DST_GLOBAL(srcname),
|
||||
// 3=DST_PTR_SP. Routed by the W1/W2/W5 N_STRUCTLIT sub-arms + W4b.
|
||||
fn cgstructlitfilltn(c: *cgen, lu: *syntax.tinfo, lit: *syntax.node,
|
||||
mode: i32, srcoff: i32, srcname: str,
|
||||
disp: i32) void = {
|
||||
let s: *syntax.tinfo = tichase(lu);
|
||||
if (s == nil) { return; };
|
||||
if (s.kind != syntax.tykind.TY_STRUCT) { return; };
|
||||
let basereg: str = "BP";
|
||||
if (mode != 0) { basereg = "BX"; };
|
||||
let totsize: i32 = s.size: i32;
|
||||
if (lit.op == syntax.tkind.TK_ELLIPSIS) {
|
||||
// `..., ...` autofill — zero the entire slot first so
|
||||
// unmentioned fields read as 0. Sized stores: 8/4/1. For
|
||||
// non-BP modes, reload BX once before the loop.
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (mode == 2) {
|
||||
emitline("\tLEAQ\t");
|
||||
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, ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + zi): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= totsize) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + zi): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < totsize) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + zi): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fieldnode: *syntax.node = lit.list;
|
||||
for (fieldnode != nil) {
|
||||
if (fieldnode.kind == syntax.nkind.N_FIELD) {
|
||||
let fname: str = fieldnode.str;
|
||||
let tf: *syntax.tfield = tfieldlookup(s, fname);
|
||||
if (tf != nil) {
|
||||
let foff: i32 = tf.offset: i32;
|
||||
let ftraw: *syntax.tinfo = tf.type_;
|
||||
let fu: *syntax.tinfo = tichase(ftraw);
|
||||
let fsz: i32 = 0;
|
||||
if (fu != nil) { fsz = fu.size: i32; };
|
||||
// Tagged-union field: delegate to the shared
|
||||
// widening writer (str/scalar/struct-lit/ident
|
||||
// payload + tagged-subset tag remap). For non-BP
|
||||
// modes, reload BX first.
|
||||
if (syntax.typeistagged(ftraw)) {
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (mode == 2) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
cgwidentaggedstore(c, ftraw,
|
||||
fieldnode.lhs, basereg,
|
||||
disp + foff, fsz);
|
||||
} else {
|
||||
// Nested struct-typed structlit value: recurse at
|
||||
// the field's offset off the resolved field tinfo
|
||||
// (NO structlookup). Pre-#17/#18 the cgexpr-then-
|
||||
// store tail would land AX = first qword and the
|
||||
// rest silently stayed zero.
|
||||
let nested: bool = false;
|
||||
if (fieldnode.lhs != nil) {
|
||||
if (fieldnode.lhs.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
if (fu != nil) {
|
||||
if (fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
cgstructlitfilltn(c, fu,
|
||||
fieldnode.lhs,
|
||||
mode, srcoff, srcname,
|
||||
disp + foff);
|
||||
nested = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Nested struct-typed CALL value (#20): cgexpr
|
||||
// leaves AX/DX/CX per #4's cgreturn ABI; the
|
||||
// choke-point stores full 8B chunks + a sized tail.
|
||||
// Guard fu.size <= 24 (#14: every in-cap tail incl.
|
||||
// 3/5/6/7); >24B falls through (sret deferred).
|
||||
// INVARIANT: between cgexpr(N_CALL) and the AX/DX/CX
|
||||
// stores, only the BX reload may intervene.
|
||||
let callwhole: bool = false;
|
||||
if (!nested) {
|
||||
if (fieldnode.lhs != nil) {
|
||||
if (fieldnode.lhs.kind == syntax.nkind.N_CALL) {
|
||||
if (fu != nil) {
|
||||
if (fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
let cfsz: i32 = fu.size: i32;
|
||||
if (cfsz <= 24) {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (mode == 2) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
cgaggregstore(c, basereg, disp + foff, cfsz, false);
|
||||
callwhole = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (nested) {
|
||||
// recursed above
|
||||
} else if (callwhole) {
|
||||
// stored above
|
||||
} else if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
|
||||
// str IS []u8: 3-word {ptr,len,cap}. cgexpr
|
||||
// leaves AX/BX/CX; for non-BP modes the dst
|
||||
// base goes in DX to dodge BX=len / CX=cap.
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
if (mode == 0) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((disp + foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((disp + foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((disp + foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
if (mode == 1) {
|
||||
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 + foff): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((disp + foff + 8): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitdispreg((disp + foff + 16): i64, "DX");
|
||||
emitline("\n");
|
||||
};
|
||||
} else if (fu != nil && fu.kind == syntax.tykind.TY_ARRAY
|
||||
&& fieldnode.lhs != nil
|
||||
&& fieldnode.lhs.kind == syntax.nkind.N_ARRLIT) {
|
||||
// #249: array field from an N_ARRLIT. element-
|
||||
// wise store at disp+foff+i*esz off the array
|
||||
// element tinfo (fu.sub). int/float elements
|
||||
// only; str/slice/struct/tagged elements are the
|
||||
// N_LET multi-word gap — loud rule-7 (cstage
|
||||
// cg_structlit_fill array arm twin).
|
||||
let eti: *syntax.tinfo = tichase(fu.sub);
|
||||
let isstructel: bool = false;
|
||||
if (eti != nil) {
|
||||
if (eti.kind == syntax.tykind.TY_STRUCT) { isstructel = true; };
|
||||
};
|
||||
if (syntax.typeisstr(fu.sub) || syntax.typeisslice(fu.sub)
|
||||
|| isstructel || syntax.typeistagged(fu.sub)) {
|
||||
let e1: str = "ww: struct-literal array field '";
|
||||
os.write(2, e1.ptr, e1.len: u64);
|
||||
os.write(2, fname.ptr, fname.len: u64);
|
||||
let e2: str = "' has a str/slice/struct/tagged element — multi-word element store out of #249 scope (N_LET array-init gap)\n";
|
||||
os.write(2, e2.ptr, e2.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let esz: i32 = 8;
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
let mop: str = storeopsz(esz);
|
||||
let isfloatel: bool = syntax.typeisfloat(fu.sub);
|
||||
let fmov: str = "MOVSD";
|
||||
if (syntax.typeisf32(fu.sub)) { fmov = "MOVSS"; };
|
||||
let idx: i32 = 0;
|
||||
let repeat: bool = false;
|
||||
let e: *syntax.node = fieldnode.lhs.list;
|
||||
for (e != nil) {
|
||||
let isellip: bool = false;
|
||||
if (e.kind == syntax.nkind.N_FIELD) {
|
||||
if (syntax.streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
isellip = true;
|
||||
};
|
||||
};
|
||||
if (isellip) {
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (mode == 2) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
let eoff: i32 = disp + foff + idx * esz;
|
||||
if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
emitline("\tX0, ");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
};
|
||||
if (mode == 0) {
|
||||
emitoff(eoff: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg(eoff: i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
if (repeat) {
|
||||
let total: i32 = fu.alen: i32;
|
||||
for (idx < total) {
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (mode == 2) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
let eoff: i32 = disp + foff + idx * esz;
|
||||
if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
emitline("\tX0, ");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
};
|
||||
if (mode == 0) {
|
||||
emitoff(eoff: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg(eoff: i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
} else if (fu != nil
|
||||
&& (fu.kind == syntax.tykind.TY_STRUCT
|
||||
|| fu.kind == syntax.tykind.TY_ARRAY
|
||||
|| fu.kind == syntax.tykind.TY_TUPLE)) {
|
||||
// #49: aggregate (struct/array/tuple) field from
|
||||
// an ADDRESSABLE source expr — `outer{.., r = r}`.
|
||||
// Funnel: source addr via aggargsrcaddr (SI),
|
||||
// field addr via LEAQ/ADDQ (BX, AFTER the source
|
||||
// walk clobbers BX/AX), then aggcopy. Width = the
|
||||
// resolved field tinfo size. Non-addressable
|
||||
// sources die loud (rule 7). cstage #49 arm twin.
|
||||
if (!aggargsrcaddr(c, fieldnode.lhs, "SI")) {
|
||||
let m49g: str = "structlit fill: aggregate field '";
|
||||
os.write(2, m49g.ptr, m49g.len: u64);
|
||||
os.write(2, fname.ptr, fname.len: u64);
|
||||
let m49h: str = "' from a non-addressable source unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49h.ptr, m49h.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (mode == 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff((disp + foff): i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
if (mode == 1) {
|
||||
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 + foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint((disp + foff): i64);
|
||||
emitline(", BX\n");
|
||||
};
|
||||
};
|
||||
aggcopy(c, fsz);
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered BX;
|
||||
// reload it before the store.
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (mode == 2) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (mode == 3) {
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
};
|
||||
if (syntax.typeisfloat(ftraw)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + foff): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
} else {
|
||||
// Full field-width store {1->MOVB, 2->MOVW,
|
||||
// 4->MOVL, else MOVQ}; the #15 sub-8 guard.
|
||||
let op: str = storeopsz(fsz);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + foff): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
fieldnode = fieldnode.next;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user