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;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
* Returns 0 if not a struct or too large. */
|
||||
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 + 16), areg(D_CX));
|
||||
} 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 {
|
||||
/* 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));
|
||||
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:
|
||||
break;
|
||||
@@ -2853,9 +2890,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* Compound: BX = load; combine with AX; store
|
||||
* BX. The asm has no RIP-relative ADDQ/SUBQ
|
||||
* mem-form, so we use the explicit load→
|
||||
* combine→store sequence uniformly. */
|
||||
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
* combine→store sequence uniformly. Narrow
|
||||
* lets go through LEAQ + indirect load with
|
||||
* 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;
|
||||
switch (n->op) {
|
||||
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
|
||||
* direct mem-form combines (ADDQ/SUBQ) are kept for
|
||||
* the simple cases; the rest go through the generic
|
||||
* register form. */
|
||||
if (n->op == TK_PLUSEQ) {
|
||||
* register form. Signed-narrow slots take the explicit
|
||||
* 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));
|
||||
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));
|
||||
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) {
|
||||
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_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;
|
||||
@@ -4100,7 +4156,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
areg(D_BX));
|
||||
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;
|
||||
}
|
||||
/* 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.
|
||||
|
||||
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):
|
||||
|
||||
- `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
|
||||
cmd/w6c/cgen.c N_LET (else branch, sz > 8 && !TY_ARRAY) and
|
||||
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.
|
||||
|
||||
@@ -6294,6 +6294,39 @@ fn loadopsz(sigd: bool, sz: i32) str = {
|
||||
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
|
||||
// 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
|
||||
@@ -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 true, base reached via LEAQ *outrootname(SB), CX).
|
||||
//
|
||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
||||
// 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.
|
||||
// Numeric out-params are i32 — offsets fit naturally and the post-#19
|
||||
// localloadop sign-extends i32 deref-stored slots on read, so negative
|
||||
// frame offsets round-trip intact.
|
||||
export fn dotchainresolve(c: *cgen, n: *node,
|
||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
||||
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i32,
|
||||
outisglobal: *bool) bool = {
|
||||
*outrootname = "";
|
||||
*outrootoff = 0i64;
|
||||
*outrootoff = 0;
|
||||
*outisglobal = false;
|
||||
*outtotaloff = 0i64;
|
||||
*outtotaloff = 0;
|
||||
*outleaffi = nil;
|
||||
*outslicedelta = -1i64;
|
||||
*outslicedelta = -1;
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind != nkind.N_DOT) { return false; };
|
||||
let stk: [16]*node;
|
||||
@@ -7933,7 +7963,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||
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 (i == 0) {
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outleaffi = found;
|
||||
return true;
|
||||
};
|
||||
@@ -7970,27 +8000,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
if (streq(ft.str, "str")) {
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
curstruct = ft.str;
|
||||
i -= 1;
|
||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
@@ -8494,17 +8524,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
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);
|
||||
emitline("(BP), AX\n");
|
||||
// str local: also load the len half into BX.
|
||||
if (isstrtype(c, lc.tnode)) {
|
||||
if (isstr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
||||
if (isslicetype(c, lc.tnode)) {
|
||||
if (issl) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
@@ -8557,7 +8595,10 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// 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;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, nm)) {
|
||||
@@ -8572,14 +8613,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
emitline("\t(CX), X0\n");
|
||||
return;
|
||||
};
|
||||
lvtnode = lv.tnode;
|
||||
lv = nil;
|
||||
} else {
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
let glop: str = localloadop(c, lvtnode);
|
||||
if (streq(glop, "MOVQ")) {
|
||||
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;
|
||||
@@ -9556,13 +9608,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Module-qualified value reference: `mod.name` where `mod`
|
||||
// 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
|
||||
// the C cgen takes when bt is NULL/tyerr.
|
||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX` for the 8B case;
|
||||
// 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.kind == nkind.N_IDENT) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, fld);
|
||||
emitline("(SB), AX\n");
|
||||
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
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;
|
||||
};
|
||||
};
|
||||
@@ -9579,26 +9643,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, n,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9609,17 +9673,17 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitdispreg((totaloff + 8): i64, "CX");
|
||||
emitline(", BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitoff((rootoff + totaloff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9634,13 +9698,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", X0\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), X0\n");
|
||||
};
|
||||
return;
|
||||
@@ -9653,13 +9717,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9936,27 +10000,27 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
if (opnd.lhs != nil) {
|
||||
if (opnd.lhs.kind == nkind.N_DOT) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, opnd,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
let extra: i64 = 0i64;
|
||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
||||
let extra: i32 = 0;
|
||||
if (slicedelta >= 0) { extra = slicedelta; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitdispreg(totaloff + extra, "CX");
|
||||
emitdispreg((totaloff + extra): i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(rootoff + totaloff + extra);
|
||||
emitoff((rootoff + totaloff + extra): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -11888,27 +11952,27 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
&& lhs.lhs.kind == nkind.N_DOT
|
||||
&& n.op == tkind.TK_ASSIGN) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let yok: bool = dotchainresolve(c, lhs,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (yok) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -11920,17 +11984,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitdispreg((totaloff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitoff((rootoff + totaloff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -11946,13 +12010,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -11966,13 +12030,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -12127,10 +12191,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lvf: *letvar = c.lets;
|
||||
let isfg: bool = false;
|
||||
let isf32g: bool = false;
|
||||
let lvftn: *node = nil;
|
||||
for (lvf != nil) {
|
||||
if (streq(lvf.name, nm)) {
|
||||
isfg = isfloattype(c, lvf.tnode);
|
||||
isf32g = isf32type(c, lvf.tnode);
|
||||
lvftn = lvf.tnode;
|
||||
lvf = nil;
|
||||
} else {
|
||||
lvf = lvf.lvnext;
|
||||
@@ -12212,9 +12278,23 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("(SB)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
// Compound RMW for a top-level let: load through
|
||||
// LEAQ + localloadop when the slot is narrow so
|
||||
// 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;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\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;
|
||||
};
|
||||
if (n.op == tkind.TK_PLUSEQ) {
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
if (n.op == tkind.TK_MINUSEQ) {
|
||||
emitline("\tSUBQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
// Pick the load width for compound RMW. Signed-narrow
|
||||
// locals must sign-extend the slot before the combine
|
||||
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
||||
// after a 4B deref-store leaves the upper bytes stale.
|
||||
let llop: str = "MOVQ";
|
||||
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
||||
if (streq(llop, "MOVQ")) {
|
||||
if (n.op == tkind.TK_PLUSEQ) {
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
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.
|
||||
emitline("\tMOVQ\t");
|
||||
emitline("\t");
|
||||
emitline(llop);
|
||||
emitline("\t");
|
||||
emitoff(off: i64);
|
||||
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_AMPEQ) { emitline("\tANDQ\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");
|
||||
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);
|
||||
emitline("(BP), AX\n");
|
||||
// str local: also load the len half into BX.
|
||||
if (isstrtype(c, lc.tnode)) {
|
||||
if (isstr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
||||
if (isslicetype(c, lc.tnode)) {
|
||||
if (issl) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
@@ -554,7 +562,10 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// 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;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, nm)) {
|
||||
@@ -569,14 +580,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
emitline("\t(CX), X0\n");
|
||||
return;
|
||||
};
|
||||
lvtnode = lv.tnode;
|
||||
lv = nil;
|
||||
} else {
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
let glop: str = localloadop(c, lvtnode);
|
||||
if (streq(glop, "MOVQ")) {
|
||||
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;
|
||||
@@ -1553,13 +1575,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Module-qualified value reference: `mod.name` where `mod`
|
||||
// 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
|
||||
// the C cgen takes when bt is NULL/tyerr.
|
||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX` for the 8B case;
|
||||
// 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.kind == nkind.N_IDENT) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, fld);
|
||||
emitline("(SB), AX\n");
|
||||
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
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;
|
||||
};
|
||||
};
|
||||
@@ -1576,26 +1610,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, n,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -1606,17 +1640,17 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitdispreg((totaloff + 8): i64, "CX");
|
||||
emitline(", BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitoff((rootoff + totaloff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
return;
|
||||
@@ -1631,13 +1665,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", X0\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), X0\n");
|
||||
};
|
||||
return;
|
||||
@@ -1650,13 +1684,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -1933,27 +1967,27 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
if (opnd.lhs != nil) {
|
||||
if (opnd.lhs.kind == nkind.N_DOT) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, opnd,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
let extra: i64 = 0i64;
|
||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
||||
let extra: i32 = 0;
|
||||
if (slicedelta >= 0) { extra = slicedelta; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitdispreg(totaloff + extra, "CX");
|
||||
emitdispreg((totaloff + extra): i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(rootoff + totaloff + extra);
|
||||
emitoff((rootoff + totaloff + extra): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -3885,27 +3919,27 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
&& lhs.lhs.kind == nkind.N_DOT
|
||||
&& n.op == tkind.TK_ASSIGN) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let yok: bool = dotchainresolve(c, lhs,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (yok) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -3917,17 +3951,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitdispreg((totaloff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitoff((rootoff + totaloff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -3943,13 +3977,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -3963,13 +3997,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -4124,10 +4158,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lvf: *letvar = c.lets;
|
||||
let isfg: bool = false;
|
||||
let isf32g: bool = false;
|
||||
let lvftn: *node = nil;
|
||||
for (lvf != nil) {
|
||||
if (streq(lvf.name, nm)) {
|
||||
isfg = isfloattype(c, lvf.tnode);
|
||||
isf32g = isf32type(c, lvf.tnode);
|
||||
lvftn = lvf.tnode;
|
||||
lvf = nil;
|
||||
} else {
|
||||
lvf = lvf.lvnext;
|
||||
@@ -4209,9 +4245,23 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("(SB)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
// Compound RMW for a top-level let: load through
|
||||
// LEAQ + localloadop when the slot is narrow so
|
||||
// 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;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\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;
|
||||
};
|
||||
if (n.op == tkind.TK_PLUSEQ) {
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
if (n.op == tkind.TK_MINUSEQ) {
|
||||
emitline("\tSUBQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
// Pick the load width for compound RMW. Signed-narrow
|
||||
// locals must sign-extend the slot before the combine
|
||||
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
||||
// after a 4B deref-store leaves the upper bytes stale.
|
||||
let llop: str = "MOVQ";
|
||||
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
||||
if (streq(llop, "MOVQ")) {
|
||||
if (n.op == tkind.TK_PLUSEQ) {
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
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.
|
||||
emitline("\tMOVQ\t");
|
||||
emitline("\t");
|
||||
emitline(llop);
|
||||
emitline("\t");
|
||||
emitoff(off: i64);
|
||||
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_AMPEQ) { emitline("\tANDQ\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";
|
||||
};
|
||||
|
||||
// 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
|
||||
// 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
|
||||
@@ -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 true, base reached via LEAQ *outrootname(SB), CX).
|
||||
//
|
||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
||||
// 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.
|
||||
// Numeric out-params are i32 — offsets fit naturally and the post-#19
|
||||
// localloadop sign-extends i32 deref-stored slots on read, so negative
|
||||
// frame offsets round-trip intact.
|
||||
export fn dotchainresolve(c: *cgen, n: *node,
|
||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
||||
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i32,
|
||||
outisglobal: *bool) bool = {
|
||||
*outrootname = "";
|
||||
*outrootoff = 0i64;
|
||||
*outrootoff = 0;
|
||||
*outisglobal = false;
|
||||
*outtotaloff = 0i64;
|
||||
*outtotaloff = 0;
|
||||
*outleaffi = nil;
|
||||
*outslicedelta = -1i64;
|
||||
*outslicedelta = -1;
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind != nkind.N_DOT) { return false; };
|
||||
let stk: [16]*node;
|
||||
@@ -2363,7 +2393,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||
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 (i == 0) {
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outleaffi = found;
|
||||
return true;
|
||||
};
|
||||
@@ -2400,27 +2430,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
if (streq(ft.str, "str")) {
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
curstruct = ft.str;
|
||||
i -= 1;
|
||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -6294,6 +6294,39 @@ fn loadopsz(sigd: bool, sz: i32) str = {
|
||||
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
|
||||
// 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
|
||||
@@ -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 true, base reached via LEAQ *outrootname(SB), CX).
|
||||
//
|
||||
// Numeric out-params are i64 so the deref-stores stay 8-byte (MOVQ).
|
||||
// `*p: *i32 = v` writes only 4 bytes via MOVL, leaving the caller's
|
||||
// 8-byte slot's upper half stale from its zero-init — and a later MOVQ
|
||||
// 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.
|
||||
// Numeric out-params are i32 — offsets fit naturally and the post-#19
|
||||
// localloadop sign-extends i32 deref-stored slots on read, so negative
|
||||
// frame offsets round-trip intact.
|
||||
export fn dotchainresolve(c: *cgen, n: *node,
|
||||
outrootname: *str, outrootoff: *i64, outtotaloff: *i64,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i64,
|
||||
outrootname: *str, outrootoff: *i32, outtotaloff: *i32,
|
||||
outleaffi: **fieldinfo, outslicedelta: *i32,
|
||||
outisglobal: *bool) bool = {
|
||||
*outrootname = "";
|
||||
*outrootoff = 0i64;
|
||||
*outrootoff = 0;
|
||||
*outisglobal = false;
|
||||
*outtotaloff = 0i64;
|
||||
*outtotaloff = 0;
|
||||
*outleaffi = nil;
|
||||
*outslicedelta = -1i64;
|
||||
*outslicedelta = -1;
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind != nkind.N_DOT) { return false; };
|
||||
let stk: [16]*node;
|
||||
@@ -7933,7 +7963,7 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == nkind.N_TNAME) {
|
||||
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 (i == 0) {
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outleaffi = found;
|
||||
return true;
|
||||
};
|
||||
@@ -7970,27 +8000,27 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
if (streq(ft.str, "str")) {
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
};
|
||||
if (primsize(ft.str) != 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
curstruct = ft.str;
|
||||
i -= 1;
|
||||
} else { if (ft.kind == nkind.N_TSLICE) {
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i64 = -1i64;
|
||||
if (streq(pseudo, "ptr")) { delta = 0i64; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8i64; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16i64; }; }; };
|
||||
if (delta < 0i64) { return false; };
|
||||
*outtotaloff = *outtotaloff + (found.foff: i64);
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + found.foff;
|
||||
*outslicedelta = delta;
|
||||
return true;
|
||||
} else {
|
||||
@@ -8494,17 +8524,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
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);
|
||||
emitline("(BP), AX\n");
|
||||
// str local: also load the len half into BX.
|
||||
if (isstrtype(c, lc.tnode)) {
|
||||
if (isstr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
||||
if (isslicetype(c, lc.tnode)) {
|
||||
if (issl) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
@@ -8557,7 +8595,10 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// 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;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, nm)) {
|
||||
@@ -8572,14 +8613,25 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
emitline("\t(CX), X0\n");
|
||||
return;
|
||||
};
|
||||
lvtnode = lv.tnode;
|
||||
lv = nil;
|
||||
} else {
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
let glop: str = localloadop(c, lvtnode);
|
||||
if (streq(glop, "MOVQ")) {
|
||||
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;
|
||||
@@ -9556,13 +9608,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Module-qualified value reference: `mod.name` where `mod`
|
||||
// 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
|
||||
// the C cgen takes when bt is NULL/tyerr.
|
||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX` for the 8B case;
|
||||
// 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.kind == nkind.N_IDENT) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, fld);
|
||||
emitline("(SB), AX\n");
|
||||
let mqop: str = localloadop(c, letvartnode(c, fld));
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
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;
|
||||
};
|
||||
};
|
||||
@@ -9579,26 +9643,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, n,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0) {
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9609,17 +9673,17 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitdispreg((totaloff + 8): i64, "CX");
|
||||
emitline(", BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitoff((rootoff + totaloff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9634,13 +9698,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", X0\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), X0\n");
|
||||
};
|
||||
return;
|
||||
@@ -9653,13 +9717,13 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -9936,27 +10000,27 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
if (opnd.lhs != nil) {
|
||||
if (opnd.lhs.kind == nkind.N_DOT) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let pok: bool = dotchainresolve(c, opnd,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (pok) {
|
||||
let extra: i64 = 0i64;
|
||||
if (slicedelta >= 0i64) { extra = slicedelta; };
|
||||
let extra: i32 = 0;
|
||||
if (slicedelta >= 0) { extra = slicedelta; };
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitdispreg(totaloff + extra, "CX");
|
||||
emitdispreg((totaloff + extra): i64, "CX");
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(rootoff + totaloff + extra);
|
||||
emitoff((rootoff + totaloff + extra): i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
return;
|
||||
@@ -11888,27 +11952,27 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
&& lhs.lhs.kind == nkind.N_DOT
|
||||
&& n.op == tkind.TK_ASSIGN) {
|
||||
let rootname: str = "";
|
||||
let rootoff: i64 = 0i64;
|
||||
let totaloff: i64 = 0i64;
|
||||
let rootoff: i32 = 0;
|
||||
let totaloff: i32 = 0;
|
||||
let leaffi: *fieldinfo = nil;
|
||||
let slicedelta: i64 = -1i64;
|
||||
let slicedelta: i32 = -1;
|
||||
let isglobal: bool = false;
|
||||
let yok: bool = dotchainresolve(c, lhs,
|
||||
&rootname, &rootoff, &totaloff,
|
||||
&leaffi, &slicedelta, &isglobal);
|
||||
if (yok) {
|
||||
if (slicedelta >= 0i64) {
|
||||
if (slicedelta >= 0) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (isglobal) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(totaloff + slicedelta, "CX");
|
||||
emitdispreg((totaloff + slicedelta): i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(rootoff + totaloff + slicedelta);
|
||||
emitoff((rootoff + totaloff + slicedelta): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -11920,17 +11984,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg(totaloff + 8i64, "CX");
|
||||
emitdispreg((totaloff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(rootoff + totaloff + 8i64);
|
||||
emitoff((rootoff + totaloff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -11946,13 +12010,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -11966,13 +12030,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(totaloff, "CX");
|
||||
emitdispreg(totaloff: i64, "CX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff(rootoff + totaloff);
|
||||
emitoff((rootoff + totaloff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
@@ -12127,10 +12191,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lvf: *letvar = c.lets;
|
||||
let isfg: bool = false;
|
||||
let isf32g: bool = false;
|
||||
let lvftn: *node = nil;
|
||||
for (lvf != nil) {
|
||||
if (streq(lvf.name, nm)) {
|
||||
isfg = isfloattype(c, lvf.tnode);
|
||||
isf32g = isf32type(c, lvf.tnode);
|
||||
lvftn = lvf.tnode;
|
||||
lvf = nil;
|
||||
} else {
|
||||
lvf = lvf.lvnext;
|
||||
@@ -12212,9 +12278,23 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("(SB)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
// Compound RMW for a top-level let: load through
|
||||
// LEAQ + localloadop when the slot is narrow so
|
||||
// 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;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\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;
|
||||
};
|
||||
if (n.op == tkind.TK_PLUSEQ) {
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
if (n.op == tkind.TK_MINUSEQ) {
|
||||
emitline("\tSUBQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
// Pick the load width for compound RMW. Signed-narrow
|
||||
// locals must sign-extend the slot before the combine
|
||||
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
||||
// after a 4B deref-store leaves the upper bytes stale.
|
||||
let llop: str = "MOVQ";
|
||||
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
||||
if (streq(llop, "MOVQ")) {
|
||||
if (n.op == tkind.TK_PLUSEQ) {
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
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.
|
||||
emitline("\tMOVQ\t");
|
||||
emitline("\t");
|
||||
emitline(llop);
|
||||
emitline("\t");
|
||||
emitoff(off: i64);
|
||||
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_AMPEQ) { emitline("\tANDQ\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
|
||||
* #19 lands. This row pins the WORKAROUND: out-param typed
|
||||
* *i64, caller reads i64 → MOVQ store/load pair agree, -16
|
||||
* round-trips intact. When #19 lands, add a sibling row that
|
||||
* exercises the natural `*p: *i32` shape and expects -16. */
|
||||
* round-trips intact. */
|
||||
{ "i64_out_param_neg_widen_deref_workaround",
|
||||
"fn setneg(p: *i64) void = { *p = -16i64; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
@@ -196,6 +195,95 @@ static const struct row rows[] = {
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user