w6c+selfhost: localloadop helper for sign-aware ident loads (closes #19)
Read-side fix dual to fldloadop: signed-narrow local/global ident loads now MOVSXD/MOVSWQ/MOVSBQ from the slot instead of raw MOVQ. Deref-stores (MOVL/MOVW/MOVB) no longer corrupt downstream i64 widens. Compound RMW restructured to gate direct-mem ADDQ/SUBQ on load_op == MOVQ. Top-level lets use LEAQ+indirect (w6a doesn't expose MOVSXD/MOVSWQ/MOVSBQ for D_EXTERN). dotchainresolve out-params restored to natural *i32 (workaround retired). selfhost/CLAUDE.md graduated.
This commit is contained in:
@@ -168,6 +168,24 @@ fldstoreop(Type *t, int sz)
|
|||||||
return A_MOVQ;
|
return A_MOVQ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* localloadop — read instruction for a scalar local/let load. Same
|
||||||
|
* dispatch as fldloadop, but keyed on the value's own type. Lets the
|
||||||
|
* caller emit MOVSXD / MOVSWQ / MOVSBQ on a signed-narrow slot instead
|
||||||
|
* of a raw MOVQ, so a slot that was last written by a narrow deref-
|
||||||
|
* store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as a
|
||||||
|
* properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
|
||||||
|
* already store the value as a sign-extended 8B word so a MOVQ read
|
||||||
|
* accidentally works; deref-stores are the only path that touches
|
||||||
|
* fewer bytes than MOVQ reads. Fixing the read makes the slot's
|
||||||
|
* representation honest regardless of which store path wrote it. */
|
||||||
|
static int
|
||||||
|
localloadop(Type *t)
|
||||||
|
{
|
||||||
|
int sz = (t && t->size > 0) ? (int)t->size : 8;
|
||||||
|
if (sz != 1 && sz != 2 && sz != 4) return A_MOVQ;
|
||||||
|
return fldloadop(t, sz);
|
||||||
|
}
|
||||||
|
|
||||||
/* struct ≤16B all-INTEGER: 1 or 2 eightbyte regs.
|
/* struct ≤16B all-INTEGER: 1 or 2 eightbyte regs.
|
||||||
* Returns 0 if not a struct or too large. */
|
* Returns 0 if not a struct or too large. */
|
||||||
static int
|
static int
|
||||||
@@ -1212,7 +1230,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX));
|
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX));
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_CX));
|
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_CX));
|
||||||
} else {
|
} else {
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
ins2(c, localloadop(n->type),
|
||||||
|
amem(D_BP, off), areg(D_AX));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Non-local: function symbols load by address (LEAQ),
|
/* Non-local: function symbols load by address (LEAQ),
|
||||||
@@ -1265,7 +1284,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, op, amem(D_CX, 0), areg(D_X0));
|
ins2(c, op, amem(D_CX, 0), areg(D_X0));
|
||||||
goto ident_done;
|
goto ident_done;
|
||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
/* Top-level lets can be the target of `*p` deref-stores
|
||||||
|
* (via `&letname: *iN`), so a signed-narrow scalar let
|
||||||
|
* needs MOVSXD/MOVSWQ/MOVSBQ on the read. Defs are
|
||||||
|
* read-only constants — their address cannot escape,
|
||||||
|
* so they keep the simpler MOVQ shape (and the wwstage
|
||||||
|
* defent registry, which doesn't track the declared
|
||||||
|
* type, agrees byte-for-byte). */
|
||||||
|
int gop = let_islet(n->str)
|
||||||
|
? localloadop(n->type) : A_MOVQ;
|
||||||
|
if (gop == A_MOVQ) {
|
||||||
|
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||||
|
} else {
|
||||||
|
/* w6a has no MOVSXD/MOVSWQ/MOVSBQ D_EXTERN
|
||||||
|
* source form, so route through a LEAQ scratch
|
||||||
|
* the same way top-level str/slice/float lets
|
||||||
|
* do. */
|
||||||
|
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
||||||
|
ins2(c, gop, amem(D_CX, 0), areg(D_AX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ident_done:
|
ident_done:
|
||||||
break;
|
break;
|
||||||
@@ -2853,9 +2890,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* Compound: BX = load; combine with AX; store
|
/* Compound: BX = load; combine with AX; store
|
||||||
* BX. The asm has no RIP-relative ADDQ/SUBQ
|
* BX. The asm has no RIP-relative ADDQ/SUBQ
|
||||||
* mem-form, so we use the explicit load→
|
* mem-form, so we use the explicit load→
|
||||||
* combine→store sequence uniformly. */
|
* combine→store sequence uniformly. Narrow
|
||||||
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
* lets go through LEAQ + indirect load with
|
||||||
areg(D_BX));
|
* localloadop so a prior `*(&letname): *iN`
|
||||||
|
* deref-store doesn't leave stale upper bytes
|
||||||
|
* in the read. */
|
||||||
|
int glop = localloadop(n->lhs->type);
|
||||||
|
if (glop == A_MOVQ) {
|
||||||
|
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
||||||
|
areg(D_BX));
|
||||||
|
} else {
|
||||||
|
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||||
|
areg(D_CX));
|
||||||
|
ins2(c, glop, amem(D_CX, 0),
|
||||||
|
areg(D_BX));
|
||||||
|
}
|
||||||
int did_compound = 1;
|
int did_compound = 1;
|
||||||
switch (n->op) {
|
switch (n->op) {
|
||||||
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
|
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
|
||||||
@@ -2894,17 +2943,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* Compound: load → combine into BX → store. The two
|
/* Compound: load → combine into BX → store. The two
|
||||||
* direct mem-form combines (ADDQ/SUBQ) are kept for
|
* direct mem-form combines (ADDQ/SUBQ) are kept for
|
||||||
* the simple cases; the rest go through the generic
|
* the simple cases; the rest go through the generic
|
||||||
* register form. */
|
* register form. Signed-narrow slots take the explicit
|
||||||
if (n->op == TK_PLUSEQ) {
|
* load-combine-store path so the load can sign-extend
|
||||||
|
* through localloadop — ADDQ/SUBQ on amem would read
|
||||||
|
* the raw 8B, which is wrong when the slot was last
|
||||||
|
* written by a 4B deref-store. */
|
||||||
|
int lop = localloadop(n->lhs->type);
|
||||||
|
if (lop == A_MOVQ && n->op == TK_PLUSEQ) {
|
||||||
ins2(c, A_ADDQ, areg(D_AX), amem(D_BP, off));
|
ins2(c, A_ADDQ, areg(D_AX), amem(D_BP, off));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n->op == TK_MINUSEQ) {
|
if (lop == A_MOVQ && n->op == TK_MINUSEQ) {
|
||||||
ins2(c, A_SUBQ, areg(D_AX), amem(D_BP, off));
|
ins2(c, A_SUBQ, areg(D_AX), amem(D_BP, off));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
ins2(c, lop, amem(D_BP, off), areg(D_BX));
|
||||||
switch (n->op) {
|
switch (n->op) {
|
||||||
|
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
|
||||||
|
case TK_MINUSEQ: ins2(c, A_SUBQ, areg(D_AX), areg(D_BX)); break;
|
||||||
case TK_STAREQ: ins2(c, A_IMULQ, areg(D_AX), areg(D_BX)); break;
|
case TK_STAREQ: ins2(c, A_IMULQ, areg(D_AX), areg(D_BX)); break;
|
||||||
case TK_AMPEQ: ins2(c, A_ANDQ, areg(D_AX), areg(D_BX)); break;
|
case TK_AMPEQ: ins2(c, A_ANDQ, areg(D_AX), areg(D_BX)); break;
|
||||||
case TK_PIPEEQ: ins2(c, A_ORQ, areg(D_AX), areg(D_BX)); break;
|
case TK_PIPEEQ: ins2(c, A_ORQ, areg(D_AX), areg(D_BX)); break;
|
||||||
@@ -4100,7 +4156,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
areg(D_BX));
|
areg(D_BX));
|
||||||
goto dot_done;
|
goto dot_done;
|
||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
/* Same gating as the bare-ident catch-all: lets route
|
||||||
|
* through localloadop (their slot can be the target of
|
||||||
|
* a narrow deref-store via `&letname: *iN`); defs and
|
||||||
|
* unresolved symbols stay on MOVQ so wwstage's defent-
|
||||||
|
* registry-without-tnode shape agrees byte-for-byte. */
|
||||||
|
int mqop = let_islet(n->str)
|
||||||
|
? localloadop(n->type) : A_MOVQ;
|
||||||
|
if (mqop == A_MOVQ) {
|
||||||
|
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||||
|
} else {
|
||||||
|
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
||||||
|
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
|
||||||
|
}
|
||||||
goto dot_done;
|
goto dot_done;
|
||||||
}
|
}
|
||||||
/* Chained N_DOT spine through value-struct fields. Handles any
|
/* Chained N_DOT spine through value-struct fields. Handles any
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ The C bootstrap's cgen has known silent-miscompilation traps. They produce wrong
|
|||||||
|
|
||||||
1. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
|
1. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
|
||||||
|
|
||||||
2. **Out-param `*p: *i32` deref-stores leave caller slot's upper 4 bytes stale.** `*p = v` lowers to MOVL (4B), but the caller's 8B slot was zero-init via MOVQ; a later i64-widening read emits MOVQ (8B raw) and zero-extends, so a negative i32 round-trips as a 4G-rooted positive i64. Default to `*i64` out-params for offsets / signed indices until task #19 lands. Symptom: garbage frame offsets like `MOVL AX, 4294967280(BP)` in stage-2 asm.
|
|
||||||
|
|
||||||
Fixed (no workaround needed):
|
Fixed (no workaround needed):
|
||||||
|
|
||||||
- `def NAME: str = "..."` field access. `.len`/`.ptr` on an Sdef ident
|
- `def NAME: str = "..."` field access. `.len`/`.ptr` on an Sdef ident
|
||||||
@@ -74,5 +72,22 @@ Fixed (no workaround needed):
|
|||||||
follow the per-index-write contract and stay uninit. See
|
follow the per-index-write contract and stay uninit. See
|
||||||
cmd/w6c/cgen.c N_LET (else branch, sz > 8 && !TY_ARRAY) and
|
cmd/w6c/cgen.c N_LET (else branch, sz > 8 && !TY_ARRAY) and
|
||||||
selfhost/cmd/wcc/cgenstmt.ww cglet no-rhs branch.
|
selfhost/cmd/wcc/cgenstmt.ww cglet no-rhs branch.
|
||||||
|
- Signed-narrow scalar reads sign-extend honestly. `*p: *i32 = v`
|
||||||
|
stores 4 bytes (MOVL — correct for a 4B pointee), and the read
|
||||||
|
side now emits MOVSXD/MOVSWQ/MOVSBQ for any signed-narrow local
|
||||||
|
or let load instead of a raw MOVQ. The natural N_ASSIGN / N_LET
|
||||||
|
store paths happened to leave the slot sign-extended so MOVQ
|
||||||
|
reads accidentally worked; deref-stores were the only path that
|
||||||
|
touched fewer bytes than MOVQ reads, so negative i32 values
|
||||||
|
round-tripped as 4G-rooted positive i64s. Cgen now routes
|
||||||
|
scalar reads through `localloadop(t)`; aliases (`type err = !i32`)
|
||||||
|
resolve the size through TBANG / TNAME / TENUM. See cmd/w6c/cgen.c
|
||||||
|
N_IDENT/N_DOT mod.name catch-alls, cgassign compound RMW for
|
||||||
|
locals + top-level lets, and the matching selfhost cgenexpr.ww
|
||||||
|
cgident / cgassign / module-qualified branches plus
|
||||||
|
cgenutil.ww `localloadop`. Top-level lets and `mod.name` go
|
||||||
|
through a LEAQ + indirect load since w6a has no MOVSXD/MOVSWQ/
|
||||||
|
MOVSBQ D_EXTERN form. dotchainresolve's out-params are back at
|
||||||
|
their natural i32 width.
|
||||||
|
|
||||||
If a port "should work" but the binary is wrong, suspect these first.
|
If a port "should work" but the binary is wrong, suspect these first.
|
||||||
|
|||||||
@@ -6294,6 +6294,39 @@ fn loadopsz(sigd: bool, sz: i32) str = {
|
|||||||
return "MOVQ";
|
return "MOVQ";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// localloadop — read instruction for a scalar local/let load. Same
|
||||||
|
// dispatch as fieldloadop, but keyed on the value's own tnode. Lets
|
||||||
|
// the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot instead
|
||||||
|
// of a raw MOVQ, so a slot that was last written by a narrow deref-
|
||||||
|
// store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as a
|
||||||
|
// properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
|
||||||
|
// store the rhs as a sign-extended 8B word, so MOVQ accidentally
|
||||||
|
// works; deref-stores are the only path that touches fewer bytes
|
||||||
|
// than MOVQ reads. Mirror of cstage's localloadop in cmd/w6c/cgen.c.
|
||||||
|
// Resolves TBANG / TENUM / TNAME-alias chains so `type err = !i32`
|
||||||
|
// picks up size 4 the same way the cstage checker pre-computes
|
||||||
|
// t->size — without this, aliased narrows fall through to MOVQ.
|
||||||
|
export fn localloadop(c: *cgen, tnode: *node) str = {
|
||||||
|
let t: *node = tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
let k: nkind = t.kind;
|
||||||
|
if (k == nkind.N_TBANG) { t = t.lhs; }
|
||||||
|
else { if (k == nkind.N_TENUM) { t = t.lhs; }
|
||||||
|
else { if (k == nkind.N_TNAME) {
|
||||||
|
let nm: str = t.str;
|
||||||
|
if (primsize(nm) > 0) { break; };
|
||||||
|
let al: *node = aliaslookup(c, nm);
|
||||||
|
if (al == nil) { break; };
|
||||||
|
t = al;
|
||||||
|
}
|
||||||
|
else { break; }; }; };
|
||||||
|
};
|
||||||
|
let sz: i32 = fieldsize(c, t);
|
||||||
|
if (sz != 1) { if (sz != 2) { if (sz != 4) { return "MOVQ"; }; }; };
|
||||||
|
let sigd: bool = fieldissignedc(c, tnode);
|
||||||
|
return loadopsz(sigd, sz);
|
||||||
|
};
|
||||||
|
|
||||||
// indexbaseesz — element size for `arr[i]` where the base is a
|
// indexbaseesz — element size for `arr[i]` where the base is a
|
||||||
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
|
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
|
||||||
// For str the element is one byte; for `[]T` / `*[]T` we drill into
|
// For str the element is one byte; for `[]T` / `*[]T` we drill into
|
||||||
@@ -7895,22 +7928,19 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
|
|||||||
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
||||||
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
||||||
//
|
//
|
||||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
// Numeric out-params are i32 — offsets fit naturally and the post-#19
|
||||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
// localloadop sign-extends i32 deref-stored slots on read, so negative
|
||||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
// frame offsets round-trip intact.
|
||||||
// read sees the zero-extended low half, so a negative i32 root offset
|
|
||||||
// would come back as a huge positive i64. Tracked as task #19; until
|
|
||||||
// it lands, callers cast to i32 at the assign sites.
|
|
||||||
export fn dotchainresolve(c: *cgen, n: *node,
|
export fn dotchainresolve(c: *cgen, n: *node,
|
||||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
|
||||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
outleaffi: **fieldinfo, outslicedelta: *i32,
|
||||||
outisglobal: *bool) bool = {
|
outisglobal: *bool) bool = {
|
||||||
*outrootname = "";
|
*outrootname = "";
|
||||||
*outrootoff = 0i64;
|
*outrootoff = 0;
|
||||||
*outisglobal = false;
|
*outisglobal = false;
|
||||||
*outtotaloff = 0i64;
|
*outtotaloff = 0;
|
||||||
*outleaffi = nil;
|
*outleaffi = nil;
|
||||||
*outslicedelta = -1i64;
|
*outslicedelta = -1;
|
||||||
if (n == nil) { return false; };
|
if (n == nil) { return false; };
|
||||||
if (n.kind != nkind.N_DOT) { return false; };
|
if (n.kind != nkind.N_DOT) { return false; };
|
||||||
let stk: [16]*node;
|
let stk: [16]*node;
|
||||||
@@ -7933,7 +7963,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
if (lc.tnode != nil) {
|
if (lc.tnode != nil) {
|
||||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||||
rootstruct = lc.tnode.str;
|
rootstruct = lc.tnode.str;
|
||||||
*outrootoff = lc.off: i64;
|
*outrootoff = lc.off;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -7960,7 +7990,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
};
|
};
|
||||||
if (found == nil) { return false; };
|
if (found == nil) { return false; };
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outleaffi = found;
|
*outleaffi = found;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -7970,27 +8000,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
if (streq(ft.str, "str")) {
|
if (streq(ft.str, "str")) {
|
||||||
if (i != 1) { return false; };
|
if (i != 1) { return false; };
|
||||||
let pseudo: str = stk[0].str;
|
let pseudo: str = stk[0].str;
|
||||||
let delta: i64 = -1i64;
|
let delta: i32 = -1;
|
||||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||||
if (delta < 0i64) { return false; };
|
if (delta < 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outslicedelta = delta;
|
*outslicedelta = delta;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (primsize(ft.str) != 0) { return false; };
|
if (primsize(ft.str) != 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
curstruct = ft.str;
|
curstruct = ft.str;
|
||||||
i -= 1;
|
i -= 1;
|
||||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||||
if (i != 1) { return false; };
|
if (i != 1) { return false; };
|
||||||
let pseudo: str = stk[0].str;
|
let pseudo: str = stk[0].str;
|
||||||
let delta: i64 = -1i64;
|
let delta: i32 = -1;
|
||||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||||
if (delta < 0i64) { return false; };
|
if (delta < 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outslicedelta = delta;
|
*outslicedelta = delta;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -8494,17 +8524,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP), X0\n");
|
emitline("(BP), X0\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// str / slice locals load (ptr[, len[, cap]]) through MOVQ
|
||||||
|
// since the header is always 8B-clean. Scalar locals route
|
||||||
|
// through localloadop so signed-narrow slots sign-extend
|
||||||
|
// after a narrow deref-store.
|
||||||
|
let isstr: bool = isstrtype(c, lc.tnode);
|
||||||
|
let issl: bool = isslicetype(c, lc.tnode);
|
||||||
|
let lop: str = "MOVQ";
|
||||||
|
if (!isstr) { if (!issl) { lop = localloadop(c, lc.tnode); }; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(lop);
|
||||||
|
emitline("\t");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
// str local: also load the len half into BX.
|
if (isstr) {
|
||||||
if (isstrtype(c, lc.tnode)) {
|
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
};
|
};
|
||||||
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
if (issl) {
|
||||||
if (isslicetype(c, lc.tnode)) {
|
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
@@ -8557,7 +8595,10 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Float global: same LEAQ-indirect shape, since MOVSS/
|
// Float global: same LEAQ-indirect shape, since MOVSS/
|
||||||
// MOVSD have no D_EXTERN operand form in w6a.
|
// MOVSD have no D_EXTERN operand form in w6a. Signed-narrow
|
||||||
|
// scalar globals route through the same LEAQ scratch since
|
||||||
|
// MOVSXD/MOVSWQ/MOVSBQ also have no D_EXTERN form.
|
||||||
|
let lvtnode: *node = nil;
|
||||||
let lv: *letvar = c.lets;
|
let lv: *letvar = c.lets;
|
||||||
for (lv != nil) {
|
for (lv != nil) {
|
||||||
if (streq(lv.name, nm)) {
|
if (streq(lv.name, nm)) {
|
||||||
@@ -8572,14 +8613,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t(CX), X0\n");
|
emitline("\t(CX), X0\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
lvtnode = lv.tnode;
|
||||||
lv = nil;
|
lv = nil;
|
||||||
} else {
|
} else {
|
||||||
lv = lv.lvnext;
|
lv = lv.lvnext;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
let glop: str = localloadop(c, lvtnode);
|
||||||
emitsymname(c, nm);
|
if (streq(glop, "MOVQ")) {
|
||||||
emitline("(SB), AX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(glop);
|
||||||
|
emitline("\t(CX), AX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9556,13 +9608,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Module-qualified value reference: `mod.name` where `mod`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX` for the 8B case;
|
||||||
// the C cgen takes when bt is NULL/tyerr.
|
// signed-narrow leaves route through LEAQ + localloadop so a
|
||||||
|
// prior narrow deref-store doesn't leave stale upper bytes. Same
|
||||||
|
// fallback the C cgen takes when bt is NULL/tyerr.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
emitline("\tMOVQ\t");
|
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||||
emitsymname(c, fld);
|
if (streq(mqop, "MOVQ")) {
|
||||||
emitline("(SB), AX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, fld);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, fld);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mqop);
|
||||||
|
emitline("\t(CX), AX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -9579,26 +9643,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_DOT) {
|
if (lhs.kind == nkind.N_DOT) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let pok: bool = dotchainresolve(c, n,
|
let pok: bool = dotchainresolve(c, n,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (pok) {
|
if (pok) {
|
||||||
if (slicedelta >= 0i64) {
|
if (slicedelta >= 0) {
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff + slicedelta, "CX");
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff + slicedelta);
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9609,17 +9673,17 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff + 8i64, "CX");
|
emitdispreg((totaloff + 8): i64, "CX");
|
||||||
emitline(", BX\n");
|
emitline(", BX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff + 8i64);
|
emitoff((rootoff + totaloff + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9634,13 +9698,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", X0\n");
|
emitline(", X0\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), X0\n");
|
emitline("(BP), X0\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9653,13 +9717,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(lop);
|
emitline(lop);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(lop);
|
emitline(lop);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9936,27 +10000,27 @@ fn cgun(c: *cgen, n: *node) void = {
|
|||||||
if (opnd.lhs != nil) {
|
if (opnd.lhs != nil) {
|
||||||
if (opnd.lhs.kind == nkind.N_DOT) {
|
if (opnd.lhs.kind == nkind.N_DOT) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let pok: bool = dotchainresolve(c, opnd,
|
let pok: bool = dotchainresolve(c, opnd,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (pok) {
|
if (pok) {
|
||||||
let extra: i64 = 0i64;
|
let extra: i32 = 0;
|
||||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
if (slicedelta >= 0) { extra = slicedelta; };
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitdispreg(totaloff + extra, "CX");
|
emitdispreg((totaloff + extra): i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitoff(rootoff + totaloff + extra);
|
emitoff((rootoff + totaloff + extra): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11888,27 +11952,27 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
&& lhs.lhs.kind == nkind.N_DOT
|
&& lhs.lhs.kind == nkind.N_DOT
|
||||||
&& n.op == tkind.TK_ASSIGN) {
|
&& n.op == tkind.TK_ASSIGN) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let yok: bool = dotchainresolve(c, lhs,
|
let yok: bool = dotchainresolve(c, lhs,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (yok) {
|
if (yok) {
|
||||||
if (slicedelta >= 0i64) {
|
if (slicedelta >= 0) {
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitdispreg(totaloff + slicedelta, "CX");
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(rootoff + totaloff + slicedelta);
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11920,17 +11984,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
emitline("\tMOVQ\tBX, ");
|
emitline("\tMOVQ\tBX, ");
|
||||||
emitdispreg(totaloff + 8i64, "CX");
|
emitdispreg((totaloff + 8): i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\tBX, ");
|
emitline("\tMOVQ\tBX, ");
|
||||||
emitoff(rootoff + totaloff + 8i64);
|
emitoff((rootoff + totaloff + 8): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11946,13 +12010,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\tX0, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\tX0, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11966,13 +12030,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(sop);
|
emitline(sop);
|
||||||
emitline("\tAX, ");
|
emitline("\tAX, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(sop);
|
emitline(sop);
|
||||||
emitline("\tAX, ");
|
emitline("\tAX, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -12127,10 +12191,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
let lvf: *letvar = c.lets;
|
let lvf: *letvar = c.lets;
|
||||||
let isfg: bool = false;
|
let isfg: bool = false;
|
||||||
let isf32g: bool = false;
|
let isf32g: bool = false;
|
||||||
|
let lvftn: *node = nil;
|
||||||
for (lvf != nil) {
|
for (lvf != nil) {
|
||||||
if (streq(lvf.name, nm)) {
|
if (streq(lvf.name, nm)) {
|
||||||
isfg = isfloattype(c, lvf.tnode);
|
isfg = isfloattype(c, lvf.tnode);
|
||||||
isf32g = isf32type(c, lvf.tnode);
|
isf32g = isf32type(c, lvf.tnode);
|
||||||
|
lvftn = lvf.tnode;
|
||||||
lvf = nil;
|
lvf = nil;
|
||||||
} else {
|
} else {
|
||||||
lvf = lvf.lvnext;
|
lvf = lvf.lvnext;
|
||||||
@@ -12212,9 +12278,23 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("(SB)\n");
|
emitline("(SB)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// Compound RMW for a top-level let: load through
|
||||||
emitsymname(c, nm);
|
// LEAQ + localloadop when the slot is narrow so
|
||||||
emitline("(SB), BX\n");
|
// a prior `*(&letname): *iN` deref-store doesn't
|
||||||
|
// leave stale upper bytes feeding the combine.
|
||||||
|
let glop: str = localloadop(c, lvftn);
|
||||||
|
if (streq(glop, "MOVQ")) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(glop);
|
||||||
|
emitline("\t(CX), BX\n");
|
||||||
|
};
|
||||||
let didcompound: bool = true;
|
let didcompound: bool = true;
|
||||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
||||||
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
||||||
@@ -12334,22 +12414,34 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (n.op == tkind.TK_PLUSEQ) {
|
// Pick the load width for compound RMW. Signed-narrow
|
||||||
emitline("\tADDQ\tAX, ");
|
// locals must sign-extend the slot before the combine
|
||||||
emitoff(off: i64);
|
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
||||||
emitline("(BP)\n");
|
// after a 4B deref-store leaves the upper bytes stale.
|
||||||
return;
|
let llop: str = "MOVQ";
|
||||||
};
|
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
||||||
if (n.op == tkind.TK_MINUSEQ) {
|
if (streq(llop, "MOVQ")) {
|
||||||
emitline("\tSUBQ\tAX, ");
|
if (n.op == tkind.TK_PLUSEQ) {
|
||||||
emitoff(off: i64);
|
emitline("\tADDQ\tAX, ");
|
||||||
emitline("(BP)\n");
|
emitoff(off: i64);
|
||||||
return;
|
emitline("(BP)\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) {
|
||||||
|
emitline("\tSUBQ\tAX, ");
|
||||||
|
emitoff(off: i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
// Generic compound: load → combine in BX → store.
|
// Generic compound: load → combine in BX → store.
|
||||||
emitline("\tMOVQ\t");
|
emitline("\t");
|
||||||
|
emitline(llop);
|
||||||
|
emitline("\t");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); };
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
||||||
|
|||||||
@@ -491,17 +491,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP), X0\n");
|
emitline("(BP), X0\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// str / slice locals load (ptr[, len[, cap]]) through MOVQ
|
||||||
|
// since the header is always 8B-clean. Scalar locals route
|
||||||
|
// through localloadop so signed-narrow slots sign-extend
|
||||||
|
// after a narrow deref-store.
|
||||||
|
let isstr: bool = isstrtype(c, lc.tnode);
|
||||||
|
let issl: bool = isslicetype(c, lc.tnode);
|
||||||
|
let lop: str = "MOVQ";
|
||||||
|
if (!isstr) { if (!issl) { lop = localloadop(c, lc.tnode); }; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(lop);
|
||||||
|
emitline("\t");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
// str local: also load the len half into BX.
|
if (isstr) {
|
||||||
if (isstrtype(c, lc.tnode)) {
|
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
};
|
};
|
||||||
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
if (issl) {
|
||||||
if (isslicetype(c, lc.tnode)) {
|
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
@@ -554,7 +562,10 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Float global: same LEAQ-indirect shape, since MOVSS/
|
// Float global: same LEAQ-indirect shape, since MOVSS/
|
||||||
// MOVSD have no D_EXTERN operand form in w6a.
|
// MOVSD have no D_EXTERN operand form in w6a. Signed-narrow
|
||||||
|
// scalar globals route through the same LEAQ scratch since
|
||||||
|
// MOVSXD/MOVSWQ/MOVSBQ also have no D_EXTERN form.
|
||||||
|
let lvtnode: *node = nil;
|
||||||
let lv: *letvar = c.lets;
|
let lv: *letvar = c.lets;
|
||||||
for (lv != nil) {
|
for (lv != nil) {
|
||||||
if (streq(lv.name, nm)) {
|
if (streq(lv.name, nm)) {
|
||||||
@@ -569,14 +580,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t(CX), X0\n");
|
emitline("\t(CX), X0\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
lvtnode = lv.tnode;
|
||||||
lv = nil;
|
lv = nil;
|
||||||
} else {
|
} else {
|
||||||
lv = lv.lvnext;
|
lv = lv.lvnext;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
let glop: str = localloadop(c, lvtnode);
|
||||||
emitsymname(c, nm);
|
if (streq(glop, "MOVQ")) {
|
||||||
emitline("(SB), AX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(glop);
|
||||||
|
emitline("\t(CX), AX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1553,13 +1575,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Module-qualified value reference: `mod.name` where `mod`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX` for the 8B case;
|
||||||
// the C cgen takes when bt is NULL/tyerr.
|
// signed-narrow leaves route through LEAQ + localloadop so a
|
||||||
|
// prior narrow deref-store doesn't leave stale upper bytes. Same
|
||||||
|
// fallback the C cgen takes when bt is NULL/tyerr.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
emitline("\tMOVQ\t");
|
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||||
emitsymname(c, fld);
|
if (streq(mqop, "MOVQ")) {
|
||||||
emitline("(SB), AX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, fld);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, fld);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mqop);
|
||||||
|
emitline("\t(CX), AX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1576,26 +1610,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_DOT) {
|
if (lhs.kind == nkind.N_DOT) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let pok: bool = dotchainresolve(c, n,
|
let pok: bool = dotchainresolve(c, n,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (pok) {
|
if (pok) {
|
||||||
if (slicedelta >= 0i64) {
|
if (slicedelta >= 0) {
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff + slicedelta, "CX");
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff + slicedelta);
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1606,17 +1640,17 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff + 8i64, "CX");
|
emitdispreg((totaloff + 8): i64, "CX");
|
||||||
emitline(", BX\n");
|
emitline(", BX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff + 8i64);
|
emitoff((rootoff + totaloff + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1631,13 +1665,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", X0\n");
|
emitline(", X0\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), X0\n");
|
emitline("(BP), X0\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1650,13 +1684,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(lop);
|
emitline(lop);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(lop);
|
emitline(lop);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1933,27 +1967,27 @@ fn cgun(c: *cgen, n: *node) void = {
|
|||||||
if (opnd.lhs != nil) {
|
if (opnd.lhs != nil) {
|
||||||
if (opnd.lhs.kind == nkind.N_DOT) {
|
if (opnd.lhs.kind == nkind.N_DOT) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let pok: bool = dotchainresolve(c, opnd,
|
let pok: bool = dotchainresolve(c, opnd,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (pok) {
|
if (pok) {
|
||||||
let extra: i64 = 0i64;
|
let extra: i32 = 0;
|
||||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
if (slicedelta >= 0) { extra = slicedelta; };
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitdispreg(totaloff + extra, "CX");
|
emitdispreg((totaloff + extra): i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitoff(rootoff + totaloff + extra);
|
emitoff((rootoff + totaloff + extra): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -3885,27 +3919,27 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
&& lhs.lhs.kind == nkind.N_DOT
|
&& lhs.lhs.kind == nkind.N_DOT
|
||||||
&& n.op == tkind.TK_ASSIGN) {
|
&& n.op == tkind.TK_ASSIGN) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let yok: bool = dotchainresolve(c, lhs,
|
let yok: bool = dotchainresolve(c, lhs,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (yok) {
|
if (yok) {
|
||||||
if (slicedelta >= 0i64) {
|
if (slicedelta >= 0) {
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitdispreg(totaloff + slicedelta, "CX");
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(rootoff + totaloff + slicedelta);
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -3917,17 +3951,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
emitline("\tMOVQ\tBX, ");
|
emitline("\tMOVQ\tBX, ");
|
||||||
emitdispreg(totaloff + 8i64, "CX");
|
emitdispreg((totaloff + 8): i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\tBX, ");
|
emitline("\tMOVQ\tBX, ");
|
||||||
emitoff(rootoff + totaloff + 8i64);
|
emitoff((rootoff + totaloff + 8): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -3943,13 +3977,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\tX0, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\tX0, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -3963,13 +3997,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(sop);
|
emitline(sop);
|
||||||
emitline("\tAX, ");
|
emitline("\tAX, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(sop);
|
emitline(sop);
|
||||||
emitline("\tAX, ");
|
emitline("\tAX, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -4124,10 +4158,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
let lvf: *letvar = c.lets;
|
let lvf: *letvar = c.lets;
|
||||||
let isfg: bool = false;
|
let isfg: bool = false;
|
||||||
let isf32g: bool = false;
|
let isf32g: bool = false;
|
||||||
|
let lvftn: *node = nil;
|
||||||
for (lvf != nil) {
|
for (lvf != nil) {
|
||||||
if (streq(lvf.name, nm)) {
|
if (streq(lvf.name, nm)) {
|
||||||
isfg = isfloattype(c, lvf.tnode);
|
isfg = isfloattype(c, lvf.tnode);
|
||||||
isf32g = isf32type(c, lvf.tnode);
|
isf32g = isf32type(c, lvf.tnode);
|
||||||
|
lvftn = lvf.tnode;
|
||||||
lvf = nil;
|
lvf = nil;
|
||||||
} else {
|
} else {
|
||||||
lvf = lvf.lvnext;
|
lvf = lvf.lvnext;
|
||||||
@@ -4209,9 +4245,23 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("(SB)\n");
|
emitline("(SB)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// Compound RMW for a top-level let: load through
|
||||||
emitsymname(c, nm);
|
// LEAQ + localloadop when the slot is narrow so
|
||||||
emitline("(SB), BX\n");
|
// a prior `*(&letname): *iN` deref-store doesn't
|
||||||
|
// leave stale upper bytes feeding the combine.
|
||||||
|
let glop: str = localloadop(c, lvftn);
|
||||||
|
if (streq(glop, "MOVQ")) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(glop);
|
||||||
|
emitline("\t(CX), BX\n");
|
||||||
|
};
|
||||||
let didcompound: bool = true;
|
let didcompound: bool = true;
|
||||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
||||||
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
||||||
@@ -4331,22 +4381,34 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (n.op == tkind.TK_PLUSEQ) {
|
// Pick the load width for compound RMW. Signed-narrow
|
||||||
emitline("\tADDQ\tAX, ");
|
// locals must sign-extend the slot before the combine
|
||||||
emitoff(off: i64);
|
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
||||||
emitline("(BP)\n");
|
// after a 4B deref-store leaves the upper bytes stale.
|
||||||
return;
|
let llop: str = "MOVQ";
|
||||||
};
|
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
||||||
if (n.op == tkind.TK_MINUSEQ) {
|
if (streq(llop, "MOVQ")) {
|
||||||
emitline("\tSUBQ\tAX, ");
|
if (n.op == tkind.TK_PLUSEQ) {
|
||||||
emitoff(off: i64);
|
emitline("\tADDQ\tAX, ");
|
||||||
emitline("(BP)\n");
|
emitoff(off: i64);
|
||||||
return;
|
emitline("(BP)\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) {
|
||||||
|
emitline("\tSUBQ\tAX, ");
|
||||||
|
emitoff(off: i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
// Generic compound: load → combine in BX → store.
|
// Generic compound: load → combine in BX → store.
|
||||||
emitline("\tMOVQ\t");
|
emitline("\t");
|
||||||
|
emitline(llop);
|
||||||
|
emitline("\t");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); };
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
||||||
|
|||||||
@@ -724,6 +724,39 @@ fn loadopsz(sigd: bool, sz: i32) str = {
|
|||||||
return "MOVQ";
|
return "MOVQ";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// localloadop — read instruction for a scalar local/let load. Same
|
||||||
|
// dispatch as fieldloadop, but keyed on the value's own tnode. Lets
|
||||||
|
// the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot instead
|
||||||
|
// of a raw MOVQ, so a slot that was last written by a narrow deref-
|
||||||
|
// store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as a
|
||||||
|
// properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
|
||||||
|
// store the rhs as a sign-extended 8B word, so MOVQ accidentally
|
||||||
|
// works; deref-stores are the only path that touches fewer bytes
|
||||||
|
// than MOVQ reads. Mirror of cstage's localloadop in cmd/w6c/cgen.c.
|
||||||
|
// Resolves TBANG / TENUM / TNAME-alias chains so `type err = !i32`
|
||||||
|
// picks up size 4 the same way the cstage checker pre-computes
|
||||||
|
// t->size — without this, aliased narrows fall through to MOVQ.
|
||||||
|
export fn localloadop(c: *cgen, tnode: *node) str = {
|
||||||
|
let t: *node = tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
let k: nkind = t.kind;
|
||||||
|
if (k == nkind.N_TBANG) { t = t.lhs; }
|
||||||
|
else { if (k == nkind.N_TENUM) { t = t.lhs; }
|
||||||
|
else { if (k == nkind.N_TNAME) {
|
||||||
|
let nm: str = t.str;
|
||||||
|
if (primsize(nm) > 0) { break; };
|
||||||
|
let al: *node = aliaslookup(c, nm);
|
||||||
|
if (al == nil) { break; };
|
||||||
|
t = al;
|
||||||
|
}
|
||||||
|
else { break; }; }; };
|
||||||
|
};
|
||||||
|
let sz: i32 = fieldsize(c, t);
|
||||||
|
if (sz != 1) { if (sz != 2) { if (sz != 4) { return "MOVQ"; }; }; };
|
||||||
|
let sigd: bool = fieldissignedc(c, tnode);
|
||||||
|
return loadopsz(sigd, sz);
|
||||||
|
};
|
||||||
|
|
||||||
// indexbaseesz — element size for `arr[i]` where the base is a
|
// indexbaseesz — element size for `arr[i]` where the base is a
|
||||||
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
|
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
|
||||||
// For str the element is one byte; for `[]T` / `*[]T` we drill into
|
// For str the element is one byte; for `[]T` / `*[]T` we drill into
|
||||||
@@ -2325,22 +2358,19 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
|
|||||||
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
||||||
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
||||||
//
|
//
|
||||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
// Numeric out-params are i32 — offsets fit naturally and the post-#19
|
||||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
// localloadop sign-extends i32 deref-stored slots on read, so negative
|
||||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
// frame offsets round-trip intact.
|
||||||
// read sees the zero-extended low half, so a negative i32 root offset
|
|
||||||
// would come back as a huge positive i64. Tracked as task #19; until
|
|
||||||
// it lands, callers cast to i32 at the assign sites.
|
|
||||||
export fn dotchainresolve(c: *cgen, n: *node,
|
export fn dotchainresolve(c: *cgen, n: *node,
|
||||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
|
||||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
outleaffi: **fieldinfo, outslicedelta: *i32,
|
||||||
outisglobal: *bool) bool = {
|
outisglobal: *bool) bool = {
|
||||||
*outrootname = "";
|
*outrootname = "";
|
||||||
*outrootoff = 0i64;
|
*outrootoff = 0;
|
||||||
*outisglobal = false;
|
*outisglobal = false;
|
||||||
*outtotaloff = 0i64;
|
*outtotaloff = 0;
|
||||||
*outleaffi = nil;
|
*outleaffi = nil;
|
||||||
*outslicedelta = -1i64;
|
*outslicedelta = -1;
|
||||||
if (n == nil) { return false; };
|
if (n == nil) { return false; };
|
||||||
if (n.kind != nkind.N_DOT) { return false; };
|
if (n.kind != nkind.N_DOT) { return false; };
|
||||||
let stk: [16]*node;
|
let stk: [16]*node;
|
||||||
@@ -2363,7 +2393,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
if (lc.tnode != nil) {
|
if (lc.tnode != nil) {
|
||||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||||
rootstruct = lc.tnode.str;
|
rootstruct = lc.tnode.str;
|
||||||
*outrootoff = lc.off: i64;
|
*outrootoff = lc.off;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -2390,7 +2420,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
};
|
};
|
||||||
if (found == nil) { return false; };
|
if (found == nil) { return false; };
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outleaffi = found;
|
*outleaffi = found;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -2400,27 +2430,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
if (streq(ft.str, "str")) {
|
if (streq(ft.str, "str")) {
|
||||||
if (i != 1) { return false; };
|
if (i != 1) { return false; };
|
||||||
let pseudo: str = stk[0].str;
|
let pseudo: str = stk[0].str;
|
||||||
let delta: i64 = -1i64;
|
let delta: i32 = -1;
|
||||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||||
if (delta < 0i64) { return false; };
|
if (delta < 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outslicedelta = delta;
|
*outslicedelta = delta;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (primsize(ft.str) != 0) { return false; };
|
if (primsize(ft.str) != 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
curstruct = ft.str;
|
curstruct = ft.str;
|
||||||
i -= 1;
|
i -= 1;
|
||||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||||
if (i != 1) { return false; };
|
if (i != 1) { return false; };
|
||||||
let pseudo: str = stk[0].str;
|
let pseudo: str = stk[0].str;
|
||||||
let delta: i64 = -1i64;
|
let delta: i32 = -1;
|
||||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||||
if (delta < 0i64) { return false; };
|
if (delta < 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outslicedelta = delta;
|
*outslicedelta = delta;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6294,6 +6294,39 @@ fn loadopsz(sigd: bool, sz: i32) str = {
|
|||||||
return "MOVQ";
|
return "MOVQ";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// localloadop — read instruction for a scalar local/let load. Same
|
||||||
|
// dispatch as fieldloadop, but keyed on the value's own tnode. Lets
|
||||||
|
// the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot instead
|
||||||
|
// of a raw MOVQ, so a slot that was last written by a narrow deref-
|
||||||
|
// store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as a
|
||||||
|
// properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
|
||||||
|
// store the rhs as a sign-extended 8B word, so MOVQ accidentally
|
||||||
|
// works; deref-stores are the only path that touches fewer bytes
|
||||||
|
// than MOVQ reads. Mirror of cstage's localloadop in cmd/w6c/cgen.c.
|
||||||
|
// Resolves TBANG / TENUM / TNAME-alias chains so `type err = !i32`
|
||||||
|
// picks up size 4 the same way the cstage checker pre-computes
|
||||||
|
// t->size — without this, aliased narrows fall through to MOVQ.
|
||||||
|
export fn localloadop(c: *cgen, tnode: *node) str = {
|
||||||
|
let t: *node = tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
let k: nkind = t.kind;
|
||||||
|
if (k == nkind.N_TBANG) { t = t.lhs; }
|
||||||
|
else { if (k == nkind.N_TENUM) { t = t.lhs; }
|
||||||
|
else { if (k == nkind.N_TNAME) {
|
||||||
|
let nm: str = t.str;
|
||||||
|
if (primsize(nm) > 0) { break; };
|
||||||
|
let al: *node = aliaslookup(c, nm);
|
||||||
|
if (al == nil) { break; };
|
||||||
|
t = al;
|
||||||
|
}
|
||||||
|
else { break; }; }; };
|
||||||
|
};
|
||||||
|
let sz: i32 = fieldsize(c, t);
|
||||||
|
if (sz != 1) { if (sz != 2) { if (sz != 4) { return "MOVQ"; }; }; };
|
||||||
|
let sigd: bool = fieldissignedc(c, tnode);
|
||||||
|
return loadopsz(sigd, sz);
|
||||||
|
};
|
||||||
|
|
||||||
// indexbaseesz — element size for `arr[i]` where the base is a
|
// indexbaseesz — element size for `arr[i]` where the base is a
|
||||||
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
|
// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice).
|
||||||
// For str the element is one byte; for `[]T` / `*[]T` we drill into
|
// For str the element is one byte; for `[]T` / `*[]T` we drill into
|
||||||
@@ -7895,22 +7928,19 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
|
|||||||
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
// (*outisglobal false, base = *outrootoff(BP)) or top-level let
|
||||||
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
// (*outisglobal true, base reached via LEAQ *outrootname(SB), CX).
|
||||||
//
|
//
|
||||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
// Numeric out-params are i32 — offsets fit naturally and the post-#19
|
||||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
// localloadop sign-extends i32 deref-stored slots on read, so negative
|
||||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
// frame offsets round-trip intact.
|
||||||
// read sees the zero-extended low half, so a negative i32 root offset
|
|
||||||
// would come back as a huge positive i64. Tracked as task #19; until
|
|
||||||
// it lands, callers cast to i32 at the assign sites.
|
|
||||||
export fn dotchainresolve(c: *cgen, n: *node,
|
export fn dotchainresolve(c: *cgen, n: *node,
|
||||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
|
||||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
outleaffi: **fieldinfo, outslicedelta: *i32,
|
||||||
outisglobal: *bool) bool = {
|
outisglobal: *bool) bool = {
|
||||||
*outrootname = "";
|
*outrootname = "";
|
||||||
*outrootoff = 0i64;
|
*outrootoff = 0;
|
||||||
*outisglobal = false;
|
*outisglobal = false;
|
||||||
*outtotaloff = 0i64;
|
*outtotaloff = 0;
|
||||||
*outleaffi = nil;
|
*outleaffi = nil;
|
||||||
*outslicedelta = -1i64;
|
*outslicedelta = -1;
|
||||||
if (n == nil) { return false; };
|
if (n == nil) { return false; };
|
||||||
if (n.kind != nkind.N_DOT) { return false; };
|
if (n.kind != nkind.N_DOT) { return false; };
|
||||||
let stk: [16]*node;
|
let stk: [16]*node;
|
||||||
@@ -7933,7 +7963,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
if (lc.tnode != nil) {
|
if (lc.tnode != nil) {
|
||||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||||
rootstruct = lc.tnode.str;
|
rootstruct = lc.tnode.str;
|
||||||
*outrootoff = lc.off: i64;
|
*outrootoff = lc.off;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -7960,7 +7990,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
};
|
};
|
||||||
if (found == nil) { return false; };
|
if (found == nil) { return false; };
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outleaffi = found;
|
*outleaffi = found;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -7970,27 +8000,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
|||||||
if (streq(ft.str, "str")) {
|
if (streq(ft.str, "str")) {
|
||||||
if (i != 1) { return false; };
|
if (i != 1) { return false; };
|
||||||
let pseudo: str = stk[0].str;
|
let pseudo: str = stk[0].str;
|
||||||
let delta: i64 = -1i64;
|
let delta: i32 = -1;
|
||||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||||
if (delta < 0i64) { return false; };
|
if (delta < 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outslicedelta = delta;
|
*outslicedelta = delta;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (primsize(ft.str) != 0) { return false; };
|
if (primsize(ft.str) != 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
curstruct = ft.str;
|
curstruct = ft.str;
|
||||||
i -= 1;
|
i -= 1;
|
||||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||||
if (i != 1) { return false; };
|
if (i != 1) { return false; };
|
||||||
let pseudo: str = stk[0].str;
|
let pseudo: str = stk[0].str;
|
||||||
let delta: i64 = -1i64;
|
let delta: i32 = -1;
|
||||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||||
if (delta < 0i64) { return false; };
|
if (delta < 0) { return false; };
|
||||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
*outtotaloff = *outtotaloff + found.foff;
|
||||||
*outslicedelta = delta;
|
*outslicedelta = delta;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -8494,17 +8524,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP), X0\n");
|
emitline("(BP), X0\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// str / slice locals load (ptr[, len[, cap]]) through MOVQ
|
||||||
|
// since the header is always 8B-clean. Scalar locals route
|
||||||
|
// through localloadop so signed-narrow slots sign-extend
|
||||||
|
// after a narrow deref-store.
|
||||||
|
let isstr: bool = isstrtype(c, lc.tnode);
|
||||||
|
let issl: bool = isslicetype(c, lc.tnode);
|
||||||
|
let lop: str = "MOVQ";
|
||||||
|
if (!isstr) { if (!issl) { lop = localloadop(c, lc.tnode); }; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(lop);
|
||||||
|
emitline("\t");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
// str local: also load the len half into BX.
|
if (isstr) {
|
||||||
if (isstrtype(c, lc.tnode)) {
|
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
};
|
};
|
||||||
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
if (issl) {
|
||||||
if (isslicetype(c, lc.tnode)) {
|
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
@@ -8557,7 +8595,10 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Float global: same LEAQ-indirect shape, since MOVSS/
|
// Float global: same LEAQ-indirect shape, since MOVSS/
|
||||||
// MOVSD have no D_EXTERN operand form in w6a.
|
// MOVSD have no D_EXTERN operand form in w6a. Signed-narrow
|
||||||
|
// scalar globals route through the same LEAQ scratch since
|
||||||
|
// MOVSXD/MOVSWQ/MOVSBQ also have no D_EXTERN form.
|
||||||
|
let lvtnode: *node = nil;
|
||||||
let lv: *letvar = c.lets;
|
let lv: *letvar = c.lets;
|
||||||
for (lv != nil) {
|
for (lv != nil) {
|
||||||
if (streq(lv.name, nm)) {
|
if (streq(lv.name, nm)) {
|
||||||
@@ -8572,14 +8613,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t(CX), X0\n");
|
emitline("\t(CX), X0\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
lvtnode = lv.tnode;
|
||||||
lv = nil;
|
lv = nil;
|
||||||
} else {
|
} else {
|
||||||
lv = lv.lvnext;
|
lv = lv.lvnext;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
let glop: str = localloadop(c, lvtnode);
|
||||||
emitsymname(c, nm);
|
if (streq(glop, "MOVQ")) {
|
||||||
emitline("(SB), AX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(glop);
|
||||||
|
emitline("\t(CX), AX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9556,13 +9608,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Module-qualified value reference: `mod.name` where `mod`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX` for the 8B case;
|
||||||
// the C cgen takes when bt is NULL/tyerr.
|
// signed-narrow leaves route through LEAQ + localloadop so a
|
||||||
|
// prior narrow deref-store doesn't leave stale upper bytes. Same
|
||||||
|
// fallback the C cgen takes when bt is NULL/tyerr.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_IDENT) {
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
emitline("\tMOVQ\t");
|
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||||
emitsymname(c, fld);
|
if (streq(mqop, "MOVQ")) {
|
||||||
emitline("(SB), AX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, fld);
|
||||||
|
emitline("(SB), AX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, fld);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mqop);
|
||||||
|
emitline("\t(CX), AX\n");
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -9579,26 +9643,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == nkind.N_DOT) {
|
if (lhs.kind == nkind.N_DOT) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let pok: bool = dotchainresolve(c, n,
|
let pok: bool = dotchainresolve(c, n,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (pok) {
|
if (pok) {
|
||||||
if (slicedelta >= 0i64) {
|
if (slicedelta >= 0) {
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff + slicedelta, "CX");
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff + slicedelta);
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9609,17 +9673,17 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitdispreg(totaloff + 8i64, "CX");
|
emitdispreg((totaloff + 8): i64, "CX");
|
||||||
emitline(", BX\n");
|
emitline(", BX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(rootoff + totaloff + 8i64);
|
emitoff((rootoff + totaloff + 8): i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9634,13 +9698,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", X0\n");
|
emitline(", X0\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), X0\n");
|
emitline("(BP), X0\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9653,13 +9717,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(lop);
|
emitline(lop);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(lop);
|
emitline(lop);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -9936,27 +10000,27 @@ fn cgun(c: *cgen, n: *node) void = {
|
|||||||
if (opnd.lhs != nil) {
|
if (opnd.lhs != nil) {
|
||||||
if (opnd.lhs.kind == nkind.N_DOT) {
|
if (opnd.lhs.kind == nkind.N_DOT) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let pok: bool = dotchainresolve(c, opnd,
|
let pok: bool = dotchainresolve(c, opnd,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (pok) {
|
if (pok) {
|
||||||
let extra: i64 = 0i64;
|
let extra: i32 = 0;
|
||||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
if (slicedelta >= 0) { extra = slicedelta; };
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitdispreg(totaloff + extra, "CX");
|
emitdispreg((totaloff + extra): i64, "CX");
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitoff(rootoff + totaloff + extra);
|
emitoff((rootoff + totaloff + extra): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11888,27 +11952,27 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
&& lhs.lhs.kind == nkind.N_DOT
|
&& lhs.lhs.kind == nkind.N_DOT
|
||||||
&& n.op == tkind.TK_ASSIGN) {
|
&& n.op == tkind.TK_ASSIGN) {
|
||||||
let rootname: str = "";
|
let rootname: str = "";
|
||||||
let rootoff: i64 = 0i64;
|
let rootoff: i32 = 0;
|
||||||
let totaloff: i64 = 0i64;
|
let totaloff: i32 = 0;
|
||||||
let leaffi: *fieldinfo = nil;
|
let leaffi: *fieldinfo = nil;
|
||||||
let slicedelta: i64 = -1i64;
|
let slicedelta: i32 = -1;
|
||||||
let isglobal: bool = false;
|
let isglobal: bool = false;
|
||||||
let yok: bool = dotchainresolve(c, lhs,
|
let yok: bool = dotchainresolve(c, lhs,
|
||||||
&rootname, &rootoff, &totaloff,
|
&rootname, &rootoff, &totaloff,
|
||||||
&leaffi, &slicedelta, &isglobal);
|
&leaffi, &slicedelta, &isglobal);
|
||||||
if (yok) {
|
if (yok) {
|
||||||
if (slicedelta >= 0i64) {
|
if (slicedelta >= 0) {
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
if (isglobal) {
|
if (isglobal) {
|
||||||
emitline("\tLEAQ\t");
|
emitline("\tLEAQ\t");
|
||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitdispreg(totaloff + slicedelta, "CX");
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(rootoff + totaloff + slicedelta);
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11920,17 +11984,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitsymname(c, rootname);
|
emitsymname(c, rootname);
|
||||||
emitline("(SB), CX\n");
|
emitline("(SB), CX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
emitline("\tMOVQ\tBX, ");
|
emitline("\tMOVQ\tBX, ");
|
||||||
emitdispreg(totaloff + 8i64, "CX");
|
emitdispreg((totaloff + 8): i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\tBX, ");
|
emitline("\tMOVQ\tBX, ");
|
||||||
emitoff(rootoff + totaloff + 8i64);
|
emitoff((rootoff + totaloff + 8): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11946,13 +12010,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\tX0, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\tX0, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -11966,13 +12030,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(sop);
|
emitline(sop);
|
||||||
emitline("\tAX, ");
|
emitline("\tAX, ");
|
||||||
emitdispreg(totaloff, "CX");
|
emitdispreg(totaloff: i64, "CX");
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
} else {
|
} else {
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(sop);
|
emitline(sop);
|
||||||
emitline("\tAX, ");
|
emitline("\tAX, ");
|
||||||
emitoff(rootoff + totaloff);
|
emitoff((rootoff + totaloff): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -12127,10 +12191,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
let lvf: *letvar = c.lets;
|
let lvf: *letvar = c.lets;
|
||||||
let isfg: bool = false;
|
let isfg: bool = false;
|
||||||
let isf32g: bool = false;
|
let isf32g: bool = false;
|
||||||
|
let lvftn: *node = nil;
|
||||||
for (lvf != nil) {
|
for (lvf != nil) {
|
||||||
if (streq(lvf.name, nm)) {
|
if (streq(lvf.name, nm)) {
|
||||||
isfg = isfloattype(c, lvf.tnode);
|
isfg = isfloattype(c, lvf.tnode);
|
||||||
isf32g = isf32type(c, lvf.tnode);
|
isf32g = isf32type(c, lvf.tnode);
|
||||||
|
lvftn = lvf.tnode;
|
||||||
lvf = nil;
|
lvf = nil;
|
||||||
} else {
|
} else {
|
||||||
lvf = lvf.lvnext;
|
lvf = lvf.lvnext;
|
||||||
@@ -12212,9 +12278,23 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("(SB)\n");
|
emitline("(SB)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// Compound RMW for a top-level let: load through
|
||||||
emitsymname(c, nm);
|
// LEAQ + localloadop when the slot is narrow so
|
||||||
emitline("(SB), BX\n");
|
// a prior `*(&letname): *iN` deref-store doesn't
|
||||||
|
// leave stale upper bytes feeding the combine.
|
||||||
|
let glop: str = localloadop(c, lvftn);
|
||||||
|
if (streq(glop, "MOVQ")) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(glop);
|
||||||
|
emitline("\t(CX), BX\n");
|
||||||
|
};
|
||||||
let didcompound: bool = true;
|
let didcompound: bool = true;
|
||||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
||||||
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
||||||
@@ -12334,22 +12414,34 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (n.op == tkind.TK_PLUSEQ) {
|
// Pick the load width for compound RMW. Signed-narrow
|
||||||
emitline("\tADDQ\tAX, ");
|
// locals must sign-extend the slot before the combine
|
||||||
emitoff(off: i64);
|
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
||||||
emitline("(BP)\n");
|
// after a 4B deref-store leaves the upper bytes stale.
|
||||||
return;
|
let llop: str = "MOVQ";
|
||||||
};
|
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
||||||
if (n.op == tkind.TK_MINUSEQ) {
|
if (streq(llop, "MOVQ")) {
|
||||||
emitline("\tSUBQ\tAX, ");
|
if (n.op == tkind.TK_PLUSEQ) {
|
||||||
emitoff(off: i64);
|
emitline("\tADDQ\tAX, ");
|
||||||
emitline("(BP)\n");
|
emitoff(off: i64);
|
||||||
return;
|
emitline("(BP)\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) {
|
||||||
|
emitline("\tSUBQ\tAX, ");
|
||||||
|
emitoff(off: i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
// Generic compound: load → combine in BX → store.
|
// Generic compound: load → combine in BX → store.
|
||||||
emitline("\tMOVQ\t");
|
emitline("\t");
|
||||||
|
emitline(llop);
|
||||||
|
emitline("\t");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP), BX\n");
|
emitline("(BP), BX\n");
|
||||||
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); };
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
||||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
||||||
|
|||||||
@@ -185,8 +185,7 @@ static const struct row rows[] = {
|
|||||||
* widens all numeric out-params to *i64 as a workaround until
|
* widens all numeric out-params to *i64 as a workaround until
|
||||||
* #19 lands. This row pins the WORKAROUND: out-param typed
|
* #19 lands. This row pins the WORKAROUND: out-param typed
|
||||||
* *i64, caller reads i64 → MOVQ store/load pair agree, -16
|
* *i64, caller reads i64 → MOVQ store/load pair agree, -16
|
||||||
* round-trips intact. When #19 lands, add a sibling row that
|
* round-trips intact. */
|
||||||
* exercises the natural `*p: *i32` shape and expects -16. */
|
|
||||||
{ "i64_out_param_neg_widen_deref_workaround",
|
{ "i64_out_param_neg_widen_deref_workaround",
|
||||||
"fn setneg(p: *i64) void = { *p = -16i64; };\n"
|
"fn setneg(p: *i64) void = { *p = -16i64; };\n"
|
||||||
"fn main() i32 = {\n"
|
"fn main() i32 = {\n"
|
||||||
@@ -196,6 +195,95 @@ static const struct row rows[] = {
|
|||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n",
|
"};\n",
|
||||||
42 },
|
42 },
|
||||||
|
/* Task #19 fix. Natural `*p: *i32` shape — `*p = -16i32` lowers
|
||||||
|
* to MOVL (correct 4B store to a 4B pointee), and the caller's
|
||||||
|
* later i64-widening read must MOVSXD the slot so the sign bit
|
||||||
|
* propagates. Without the fix, the MOVQ read of the slot returns
|
||||||
|
* 0x00000000_FFFFFFF0 (4294967280) and the equality fails. */
|
||||||
|
{ "i32_out_param_neg_widen_deref",
|
||||||
|
"fn setneg(p: *i32) void = { *p = -16i32; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: i32 = 0i32;\n"
|
||||||
|
" setneg(&x);\n"
|
||||||
|
" let z: i64 = x: i64;\n"
|
||||||
|
" if (z == -16i64) { return 42; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
|
/* Task #19 fix, i16 sibling — `*p: *i16 = -16i16` writes 2B,
|
||||||
|
* caller widens to i64. Without MOVSWQ the read sees 0xFFF0
|
||||||
|
* (65520). */
|
||||||
|
{ "i16_out_param_neg_widen_deref",
|
||||||
|
"fn setneg(p: *i16) void = { *p = -16i64: i16; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: i16 = 0i16;\n"
|
||||||
|
" setneg(&x);\n"
|
||||||
|
" let z: i64 = x: i64;\n"
|
||||||
|
" if (z == -16i64) { return 42; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
|
/* Task #19 fix, i8 sibling — `*p: *i8 = -16i8` writes 1B, caller
|
||||||
|
* widens to i64. Without MOVSBQ the read sees 0xF0 (240). */
|
||||||
|
{ "i8_out_param_neg_widen_deref",
|
||||||
|
"fn setneg(p: *i8) void = { *p = -16i64: i8; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: i8 = 0i8;\n"
|
||||||
|
" setneg(&x);\n"
|
||||||
|
" let z: i64 = x: i64;\n"
|
||||||
|
" if (z == -16i64) { return 42; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
|
/* Task #19 fix, top-level let target. Same shape but the local
|
||||||
|
* `x` is replaced by a top-level let so the read goes through
|
||||||
|
* the RIP-relative path. Without the fix the read is MOVQ from
|
||||||
|
* &x(SB) and sees zero-extended garbage. */
|
||||||
|
{ "i32_global_neg_widen_deref",
|
||||||
|
"let g: i32 = 0i32;\n"
|
||||||
|
"fn setneg(p: *i32) void = { *p = -16i32; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" setneg(&g);\n"
|
||||||
|
" let z: i64 = g: i64;\n"
|
||||||
|
" if (z == -16i64) { return 42; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
|
/* Task #19 fix, compound RMW on a narrow local that was last
|
||||||
|
* written via a deref-store. Pins the cgassign restructure that
|
||||||
|
* routes the load half of `x OP= v` through localloadop (so the
|
||||||
|
* upper bytes feeding the combine come from a sign-extending
|
||||||
|
* load, not raw 8B off the slot) and gates the direct-mem
|
||||||
|
* ADDQ/SUBQ shortcut on `load_op == MOVQ`. Reads x as i32 to
|
||||||
|
* exercise the in-band path without an explicit i64 widen. */
|
||||||
|
{ "i32_compound_rmw_after_deref_store",
|
||||||
|
"fn setv(p: *i32, v: i32) void = { *p = v; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: i32 = 0i32;\n"
|
||||||
|
" setv(&x, 0x7FFFFFFEi32);\n"
|
||||||
|
" x += 1i32;\n"
|
||||||
|
" if (x == 0x7FFFFFFFi32) { return 42; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
|
/* Task #19 fix, aliased narrow read. `type myinv = !i32` wraps
|
||||||
|
* i32 with the err flag — its tnode is N_TBANG(N_TNAME("i32")).
|
||||||
|
* Pins the TBANG/TENUM/TNAME → aliaslookup loop inside the
|
||||||
|
* wwstage `localloadop`: without it `fieldsize` falls back to 8
|
||||||
|
* on the bare alias name and the read picks MOVQ, so the deref-
|
||||||
|
* stored -16 round-trips as 4294967280 instead of -16. cstage
|
||||||
|
* resolves the alias through Type.size and type_isunsigned. */
|
||||||
|
{ "alias_bang_i32_widen_deref",
|
||||||
|
"type myinv = !i32;\n"
|
||||||
|
"fn setneg(p: *myinv) void = { *p = -16i64: myinv; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: myinv = 0i64: myinv;\n"
|
||||||
|
" setneg(&x);\n"
|
||||||
|
" let z: i64 = x: i64;\n"
|
||||||
|
" if (z == -16i64) { return 42; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|||||||
Reference in New Issue
Block a user