cstage+selfhost+test: cgen N_ASSIGN whole-STRUCT (call+structlit, 5 sites)
Receive side of #4's cgreturn ABI (aee8149) for TY_STRUCT lvalues of size <=24B. Producer materialises rhs into AX=bytes[0..7], DX=[8..15], CX=[16..23], zero-padded to 24B; receive sites here read the regs and write only `declared sz` bytes — MOVQ for full 8B chunks plus a sized tail (MOVL/MOVW/MOVB) by the *declared* struct size. ASYMMETRY: do NOT mirror the sender's three uniform MOVQs, else trailing 1..7B chunks overrun the next local slot. Tail chunks in {3,5,6,7} are unreachable under WW struct align rules (size%align==0) and fall through. Five sites wired in each stage (cstage cgen.c, wwstage cgenexpr.ww + cgenstmt.ww), call-result + structlit rhs at each: - N_LET `let s: T = bar()` / `= T{...}` cgenstmt cglet - N_ASSIGN N_IDENT-lhs `s = bar()` / `= T{...}` cgenexpr cgassign - N_ASSIGN single-DOT local-base `o.f = ...` - N_ASSIGN single-DOT ptr-base auto-deref `p.f = ...` - N_ASSIGN single-DOT global-base `g.f = ...` - N_ASSIGN chained-DOT depth>=2 `o.m.in = ...` (The four dot-flavors share one shape pattern, hence "5 sites".) Where the dst addr needs scratch (ptr-base/global-base/via_cx), it is loaded into BX after the call so CX stays as the third value word; for structlit field-walks BX is reloaded before each store since cgexpr clobbers AX/BX between fields. wwstage needed a new `structnaturalsize(si)` helper (cgenutil.ww): si.totsize is mis-named — it's slot-padded to 8 by registerstruct for stack-slot use, while the receive ABI wants the type's natural size (max(foff+fsz)). Splitting si.totsize into naturalsize + slotsize is tracked as the wwstage struct sizing follow-up (task #15); until that lands, the helper recovers the natural size at receive sites. Test 701_cgassign_struct.c (18 rows, 3 checks each — cstage value, wwstage value, asm byte-identity), wired in Makefile after 698. The headline ASYMMETRY case is the 20B `{i32×5}` row: sender pads to 24B via three MOVQs, receiver writes MOVQ AX +0, MOVQ DX +8, MOVL CX +16. A regression to a MOVQ tail there overruns 4B past the slot and flips the exit-code check. smoke.combined.ww is the auto-regen ride-along of strings.freeall landing in714d089(worker-shlex). Pre-existing gaps surfaced and tracked separately (not fixed here, out of scope): - task #16: silent drop of `(*p).f = ...` explicit-deref dot lhs. - task #17: silent zero of nested STRUCTLIT field in N_LET / N_ASSIGN initializer — the field_chain and field_global test rows use explicit field writes (`o.m.t = 10i64;`) rather than nested literals as a fixture-level workaround. - task #9: module-name-mangle for fn labels avoided in the field_global_call fixture by `let g: outer;` (no init). make test: 59/59. 994_w6c_ww + 995_self_rebuild PASS — bootstrap byte-identity is the load-bearing proof for this commit's scope.
This commit is contained in:
@@ -476,6 +476,32 @@ export fn dup(s: str) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// freeall — release every str element in `s` (those that were
|
||||
// individually allocated) plus the slice's backing storage. Mirrors
|
||||
// Hare's strings::freeall — the natural disposer for any function
|
||||
// returning a fresh `[]str` of dup'd elements (e.g. shlex.split).
|
||||
//
|
||||
// Each element is freed via os.free at its own length; the slice
|
||||
// header storage is freed at `cap * 16` bytes (one str = 16B). Empty
|
||||
// elements (`{nil, 0}` from a zero-length dup) are skipped — calling
|
||||
// os.free on a nil pointer at len 0 would tickle the rt_free guard
|
||||
// that the runtime treats as a logic bug.
|
||||
//
|
||||
// `cap == 0` means the slice was never grown (empty `[]str` with no
|
||||
// backing allocation); skip the header free in that case too.
|
||||
export fn freeall(s: []str) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i].len > 0) {
|
||||
os.free(s[i].ptr: *void, s[i].len: u64);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (s.cap > 0) {
|
||||
os.free(s.ptr: *void, (s.cap: u64) * 16u64);
|
||||
};
|
||||
};
|
||||
|
||||
// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's
|
||||
// strings::rbyteindex. Rune needle scans for the byte that encodes it
|
||||
// (ASCII only); str needle scans for the substring. Empty str needle
|
||||
@@ -6891,6 +6917,31 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
|
||||
|
||||
// ---- type-driven slot sizing ----------------------------------------
|
||||
|
||||
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
|
||||
// fsz) across declared fields. Mirrors cstage's `lu->size` for a
|
||||
// TY_STRUCT (rounded only to the struct's maxalign).
|
||||
//
|
||||
// NOTE: si.totsize is mis-named — it's actually the *slot-padded*
|
||||
// size (rounded up to 8 for stack-slot use; see registerstruct's
|
||||
// tail `if ((off & 7) != 0) ...`). Frame allocation, [N]foo stride,
|
||||
// and similar consumers want that slot-padded number. The
|
||||
// receive-side ABI (#5) and any future "TYPE size, not slot size"
|
||||
// query wants the natural size. Until si.totsize is split into
|
||||
// si.naturalsize + si.slotsize (tracked as the wwstage-sizing
|
||||
// follow-up task), recover the type-natural size from the field
|
||||
// chain here.
|
||||
fn structnaturalsize(si: *structinfo) i32 = {
|
||||
if (si == nil) { return 0; };
|
||||
let n: i32 = 0;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let end: i32 = fi.foff + fi.fsz;
|
||||
if (end > n) { n = end; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
// Exact match first: bare-from-source struct names and already-
|
||||
// leafed lookups hit here directly.
|
||||
@@ -12250,11 +12301,148 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
n.rhs, "BX", fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs through
|
||||
// *struct base: cgexpr can't materialise a whole
|
||||
// struct value, so word-copy from the rhs slot
|
||||
// to *(struct_ptr + fi.foff). Other rhs shapes
|
||||
// (call result, struct literal) tracked as #27.
|
||||
// struct-typed field via *struct base — three
|
||||
// rhs shapes (call/structlit added with #5;
|
||||
// closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
|
||||
// ABI; load *struct ptr into BX after the
|
||||
// call, sized stores per natural struct size.
|
||||
// N_STRUCTLIT: field-walk; reload BX before
|
||||
// each store so cgexpr can clobber AX/BX.
|
||||
// si.totsize is slot-padded; use
|
||||
// structnaturalsize for the type-size query.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12411,10 +12599,134 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
n.rhs, "BP", lc.off + fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs:
|
||||
// word-copy direct, skipping cgexpr (no
|
||||
// register convention for a whole struct
|
||||
// value). #27 covers non-ident rhs.
|
||||
// struct-typed field on a direct struct
|
||||
// local — three rhs shapes (call/structlit
|
||||
// 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).
|
||||
// N_STRUCTLIT: field-walk; each inner
|
||||
// field stored at +fi.foff+inner_foff(BP).
|
||||
// BP-rel direct, no addr scratch needed.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12593,10 +12905,144 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// struct-typed field, struct-ident rhs on global
|
||||
// struct base: word-copy direct. Skips cgexpr —
|
||||
// no register convention for a whole struct value.
|
||||
// #27 covers non-ident rhs.
|
||||
// struct-typed field on a global struct base —
|
||||
// three rhs shapes (call/structlit added with
|
||||
// #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; LEAQ base into BX
|
||||
// after call, sized stores per natural size.
|
||||
// N_STRUCTLIT: field-walk; reload BX per store.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12883,11 +13329,236 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// TY_STRUCT terminal: word-copy the rhs slot onto
|
||||
// base+totaloff. Only N_IDENT rhs is wired — other
|
||||
// shapes (call result, struct literal) route through
|
||||
// #27. cgexpr is skipped (no whole-struct register
|
||||
// convention); reads come straight from the rhs slot.
|
||||
// TY_STRUCT terminal: three rhs shapes:
|
||||
// - N_IDENT: word-copy from the rhs local slot
|
||||
// (cgexpr is skipped — no whole-struct register
|
||||
// convention for an arbitrary local).
|
||||
// - N_CALL (added with #5): cgexpr leaves the
|
||||
// value in AX/DX/CX per #4's cgreturn ABI; sized
|
||||
// stores write only the declared field size.
|
||||
// cgreturn touches only AX/DX/CX so for
|
||||
// ptrroot/global we load the dst addr into BX
|
||||
// (not CX) after the call to keep CX as the
|
||||
// third value word.
|
||||
// - N_STRUCTLIT (added with #5): field-by-field
|
||||
// store; for ptrroot/global the dst addr is
|
||||
// reloaded into BX before each store so cgexpr
|
||||
// can clobber AX/BX between fields.
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (lsz <= 24) {
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
};
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
} else {
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
&& leaffi.tnode != nil
|
||||
@@ -13306,6 +13977,150 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Struct-typed local reassignment: `s = expr;` where s
|
||||
// is a TY_STRUCT local of size <=24B. Two rhs shapes
|
||||
// (mirrors cglet's N_STRUCTLIT and the call-result
|
||||
// receive branch):
|
||||
// - N_STRUCTLIT: walk fields, store at off+foff
|
||||
// directly. ASYMMETRY-safe (no register copy from
|
||||
// the caller; values come from cgexpr).
|
||||
// - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
// declared struct size — MOVQ for full 8B chunks
|
||||
// plus MOVL/MOVW/MOVB tail. See cglet receive
|
||||
// site for the ASYMMETRY rationale.
|
||||
// Struct-IDENT word-copy rhs (s = p) is left unwired;
|
||||
// #5 is scoped to receive-side of #4 (calls + literals).
|
||||
// fsz dispatch uses the explicit {1→MOVB, 4→MOVL, else
|
||||
// MOVQ} pattern (not fieldstoreop) to match cstage
|
||||
// cgen.c N_ASSIGN byte-identically — wwstage's
|
||||
// fieldstoreop returns MOVW for fsz==2 which cstage
|
||||
// doesn't emit (tracked separately as the cstage/
|
||||
// wwstage MOVW divergence task).
|
||||
if (lcn != nil) {
|
||||
let lctn: *node = lcn.tnode;
|
||||
let lcsname: str;
|
||||
lcsname.ptr = nil; lcsname.len = 0;
|
||||
if (lctn != nil) {
|
||||
if (lctn.kind == nkind.N_TNAME) {
|
||||
lcsname = lctn.str;
|
||||
};
|
||||
};
|
||||
if (lcsname.len > 0) {
|
||||
let lcsi: *structinfo = structlookup(c, lcsname);
|
||||
if (lcsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lcnsz: i32 = structnaturalsize(lcsi);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lcsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lcsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lcsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lcsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = lcsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Compound (+= -= *= /=) loads
|
||||
// slot into X1, combines into X1, stores X1 back —
|
||||
@@ -14080,6 +14895,79 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Whole-struct receive for sizes <=24B (call-result rhs).
|
||||
// Counterpart of #4's cgreturn ABI: cgexpr leaves
|
||||
// AX=bytes[0..7], DX=bytes[8..15], CX=bytes[16..23],
|
||||
// zero-padded to 24B by the producer.
|
||||
//
|
||||
// ASYMMETRY (do NOT mirror the sender): producer emits three
|
||||
// uniform MOVQs into a zero-padded 24B scratch slot; the
|
||||
// receiver writes only `sz` bytes — MOVQ for full 8B chunks
|
||||
// plus a sized tail (MOVL/MOVW/MOVB) by the *declared*
|
||||
// struct size. Otherwise a trailing 1..7-byte chunk would
|
||||
// overrun into the next local slot.
|
||||
//
|
||||
// Tail chunks in {3,5,6,7} (unreachable under WW struct
|
||||
// alignment rules — field aligns force size%align==0) fall
|
||||
// through to the generic scalar store rather than emit a
|
||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||
// deferred, same constraint as #4). Mirrors the cstage
|
||||
// cgen.c N_LET receive branch.
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TNAME) {
|
||||
sname = tn.str;
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let lsi: *structinfo = structlookup(c, sname);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8) for
|
||||
// stack-slot use; the receive ABI needs the
|
||||
// TYPE's natural size — see structnaturalsize.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (lsz <= 24) {
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, rhs);
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
|
||||
Reference in New Issue
Block a user