w6c: keep unwrap destination off the call stack
This commit is contained in:
@@ -39,6 +39,11 @@ static int *cg_frame;
|
|||||||
* semantics for synthetic scratches). 0 means "not yet allocated";
|
* semantics for synthetic scratches). 0 means "not yet allocated";
|
||||||
* negative offsets returned by local_alloc are the live value. */
|
* negative offsets returned by local_alloc are the live value. */
|
||||||
static int cg_retscr;
|
static int cg_retscr;
|
||||||
|
/* The aggregate-unwrap receiver must survive its producer call without
|
||||||
|
* perturbing SP: an outstanding PUSHQ at CALL violates SysV alignment.
|
||||||
|
* One BP-relative address spill per function mirrors wwstage's @-prefix
|
||||||
|
* scratch reuse. */
|
||||||
|
static int cg_unwrapdst;
|
||||||
/* Per-fn @tupfscr offset (single-slot SSoT). A multi-float tuple return
|
/* Per-fn @tupfscr offset (single-slot SSoT). A multi-float tuple return
|
||||||
* (#164/#107) spills each float out of X0 to this scratch as the L→R
|
* (#164/#107) spills each float out of X0 to this scratch as the L→R
|
||||||
* element walk clobbers X0, then reloads X0/X1 by SSE index after the
|
* element walk clobbers X0, then reloads X0/X1 by SSE index after the
|
||||||
@@ -5362,9 +5367,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
fatal("#16: global/chained aggregate "
|
fatal("#16: global/chained aggregate "
|
||||||
"unwrap field dest unresolved "
|
"unwrap field dest unresolved "
|
||||||
"(cgplaceaddr)");
|
"(cgplaceaddr)");
|
||||||
ins1(c, A_PUSHQ, areg(D_BX));
|
int dstscr;
|
||||||
|
if (cg_unwrapdst != 0) {
|
||||||
|
dstscr = cg_unwrapdst;
|
||||||
|
} else {
|
||||||
|
dstscr = local_alloc(c, &locals, "@unwrapdst",
|
||||||
|
8, cg_frame);
|
||||||
|
cg_unwrapdst = dstscr;
|
||||||
|
}
|
||||||
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, dstscr));
|
||||||
cgexpr(c, n->rhs, locals);
|
cgexpr(c, n->rhs, locals);
|
||||||
ins1(c, A_POPQ, areg(D_BX));
|
ins2(c, A_MOVQ, amem(D_BP, dstscr), areg(D_BX));
|
||||||
cg_agg_reg_store(c, &locals, D_BX, 0, ssz, 0);
|
cg_agg_reg_store(c, &locals, D_BX, 0, ssz, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -5377,9 +5390,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* explicit-deref form `(*p).f = ...` where the parser emits
|
* explicit-deref form `(*p).f = ...` where the parser emits
|
||||||
* N_UN(STAR, IDENT(p)). For (*p).f, retarget base to the
|
* N_UN(STAR, IDENT(p)). For (*p).f, retarget base to the
|
||||||
* inner IDENT so the via_ptr branch fires identically to
|
* inner IDENT so the via_ptr branch fires identically to
|
||||||
* `p.f = v`. v1 scope: bare-IDENT inner only; (*expr).f
|
* `p.f = v`. Other addressable bases fall through to the
|
||||||
* (non-IDENT inner) falls through to the existing drop
|
* shared place resolver. */
|
||||||
* behaviour pending follow-up task. */
|
|
||||||
if (!global_ptr_field_decline(n->lhs, n->op, locals)
|
if (!global_ptr_field_decline(n->lhs, n->op, locals)
|
||||||
&& n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs &&
|
&& n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs &&
|
||||||
(n->lhs->lhs->kind == N_IDENT ||
|
(n->lhs->lhs->kind == N_IDENT ||
|
||||||
@@ -5452,20 +5464,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (strcmp(fl->name, n->lhs->str) == 0)
|
if (strcmp(fl->name, n->lhs->str) == 0)
|
||||||
{ f = fl; break; }
|
{ f = fl; break; }
|
||||||
if (f == NULL) break;
|
if (f == NULL) break;
|
||||||
/* Tagged-union field: synthesise tag and store
|
/* Compound ops on tagged fields are not meaningful,
|
||||||
* value bytes. Compound ops on tagged fields are
|
* so only plain `=` is wired. */
|
||||||
* not meaningful, so only plain `=` is wired.
|
|
||||||
* Three base shapes:
|
|
||||||
* - via_ptr: base is *struct local; address
|
|
||||||
* pre-loaded into BX. Buggy with a str
|
|
||||||
* variant since cgexpr will overwrite BX,
|
|
||||||
* but matches the existing pre-global
|
|
||||||
* behaviour.
|
|
||||||
* - is_global: struct global. LEAQ after
|
|
||||||
* cgexpr drops the slot address into CX
|
|
||||||
* without touching AX/BX, so str variants
|
|
||||||
* work cleanly.
|
|
||||||
* - else: struct local, BP-relative. */
|
|
||||||
Type *ft = f->type;
|
Type *ft = f->type;
|
||||||
/* Transitive chase (#5-F1 fold): 2-level alias
|
/* Transitive chase (#5-F1 fold): 2-level alias
|
||||||
* slice/str field skipped the 3-word arm — ptr
|
* slice/str field skipped the 3-word arm — ptr
|
||||||
@@ -11876,10 +11876,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* IDENT(p)) with type T (post-deref struct). Pull the inner
|
* IDENT(p)) with type T (post-deref struct). Pull the inner
|
||||||
* IDENT in as dot_lhs so bt resolves to *T and the pointer-
|
* IDENT in as dot_lhs so bt resolves to *T and the pointer-
|
||||||
* auto-deref branch below fires (mirror of the N_ASSIGN
|
* auto-deref branch below fires (mirror of the N_ASSIGN
|
||||||
* N_DOT lhs retarget). v1 scope: N_IDENT inner only;
|
* N_DOT lhs retarget). Branches that gate on
|
||||||
* (*expr).f follow-up task pending. Branches that gate on
|
|
||||||
* `n->lhs->kind == N_DOT/N_INDEX/...` keep checking the raw
|
* `n->lhs->kind == N_DOT/N_INDEX/...` keep checking the raw
|
||||||
* n->lhs since (*p) isn't either of those shapes. */
|
* n->lhs since (*p) isn't either of those shapes; remaining
|
||||||
|
* addressable bases use the shared place resolver. */
|
||||||
Node *dot_lhs = n->lhs;
|
Node *dot_lhs = n->lhs;
|
||||||
if (dot_lhs && dot_lhs->kind == N_UN && dot_lhs->op == TK_STAR
|
if (dot_lhs && dot_lhs->kind == N_UN && dot_lhs->op == TK_STAR
|
||||||
&& dot_lhs->lhs && dot_lhs->lhs->kind == N_IDENT)
|
&& dot_lhs->lhs && dot_lhs->lhs->kind == N_IDENT)
|
||||||
@@ -15803,6 +15803,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
|||||||
nloops = 0;
|
nloops = 0;
|
||||||
cg_ret_type = fn->type ? fn->type->ret : NULL;
|
cg_ret_type = fn->type ? fn->type->ret : NULL;
|
||||||
cg_retscr = 0;
|
cg_retscr = 0;
|
||||||
|
cg_unwrapdst = 0;
|
||||||
cg_tupfscr = 0;
|
cg_tupfscr = 0;
|
||||||
cg_tupargscr = 0;
|
cg_tupargscr = 0;
|
||||||
cg_tupargscr_sz = 0;
|
cg_tupargscr_sz = 0;
|
||||||
|
|||||||
@@ -3623,10 +3623,10 @@ fn cgdot(c: *cgen, n: *syntax.node) void = {
|
|||||||
// IDENT(p)). Substitute the inner IDENT as dotlhs so the
|
// IDENT(p)). Substitute the inner IDENT as dotlhs so the
|
||||||
// pointer-auto-deref branch (lhs.kind == N_IDENT && N_TPTR
|
// pointer-auto-deref branch (lhs.kind == N_IDENT && N_TPTR
|
||||||
// tnode) fires the same as `p.f`. Mirror of the N_ASSIGN N_DOT
|
// tnode) fires the same as `p.f`. Mirror of the N_ASSIGN N_DOT
|
||||||
// lhs retarget in cgassign. v1 scope: N_IDENT inner only;
|
// lhs retarget in cgassign. Enum-leaf lookup above and
|
||||||
// (*expr).f follow-up task pending. Enum-leaf lookup above and
|
|
||||||
// chained-N_DOT branches below keep checking raw lhs since
|
// chained-N_DOT branches below keep checking raw lhs since
|
||||||
// (*p) is neither shape.
|
// (*p) is neither shape; remaining addressable bases use the
|
||||||
|
// shared place resolver.
|
||||||
let dotlhs: *syntax.node = lhs;
|
let dotlhs: *syntax.node = lhs;
|
||||||
if (dotlhs != nil) {
|
if (dotlhs != nil) {
|
||||||
if (dotlhs.kind == syntax.nkind.N_UN) {
|
if (dotlhs.kind == syntax.nkind.N_UN) {
|
||||||
@@ -8780,9 +8780,14 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
os.write(2, m16.ptr, m16.len: u64);
|
os.write(2, m16.ptr, m16.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
emitline("\tPUSHQ\tBX\n");
|
let dstscr: i32 = localadd(c, "@unwrapdst", 8, nil);
|
||||||
|
emitline("\tMOVQ\tBX, ");
|
||||||
|
emitoff(dstscr: i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
emitline("\tPOPQ\tBX\n");
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(dstscr: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
cgaggregstore(c, "BX", 0, ssz, false);
|
cgaggregstore(c, "BX", 0, ssz, false);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -10876,7 +10881,8 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
// is rare and not yet needed by our fixtures). Base accepts the
|
// is rare and not yet needed by our fixtures). Base accepts the
|
||||||
// explicit-deref form `(*p).f = ...` (parser N_UN(STAR, IDENT))
|
// explicit-deref form `(*p).f = ...` (parser N_UN(STAR, IDENT))
|
||||||
// by retargeting to the inner IDENT so the via_ptr branch fires
|
// by retargeting to the inner IDENT so the via_ptr branch fires
|
||||||
// the same as auto-deref `p.f = v`. v1 scope: bare-IDENT inner.
|
// the same as auto-deref `p.f = v`. Other addressable bases use
|
||||||
|
// the shared place resolver below.
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
if (lhs.kind == syntax.nkind.N_DOT) {
|
if (lhs.kind == syntax.nkind.N_DOT) {
|
||||||
let base: *syntax.node = lhs.lhs;
|
let base: *syntax.node = lhs.lhs;
|
||||||
|
|||||||
@@ -271,3 +271,81 @@ fn dotbaserow(label: str, src: str, needle: str) void = {
|
|||||||
"export fn main() i32 = { return 0; };\n"),
|
"export fn main() i32 = { return 0; };\n"),
|
||||||
"\tMOVQ\t$32, CX\n");
|
"\tMOVQ\t$32, CX\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn unwrapaligncheck(label: str, stage: str, s: str) void = {
|
||||||
|
let w: str = probewindow(label, stage, s);
|
||||||
|
let call: i32 = testenv.pos(w, "\tCALL\tmain.mkpair(SB)\n");
|
||||||
|
if (call < 0) {
|
||||||
|
fail(label, strings.concat(stage, ": no CALL main.mkpair"));
|
||||||
|
};
|
||||||
|
let pre: str = strings.sub(w, 0, call);
|
||||||
|
let post: str = strings.sub(w, call, w.len);
|
||||||
|
if (testenv.has(pre, "\tPUSHQ\tBX\n")) {
|
||||||
|
fail(label, strings.concat(stage,
|
||||||
|
": destination PUSHQ remains outstanding at CALL"));
|
||||||
|
};
|
||||||
|
if (!testenv.has(pre, "\tMOVQ\tBX, -")) {
|
||||||
|
fail(label, strings.concat(stage,
|
||||||
|
": destination address is not spilled BP-relative"));
|
||||||
|
};
|
||||||
|
if (!testenv.has(post, "\tMOVQ\t-")
|
||||||
|
|| !testenv.has(post, "(BP), BX\n")) {
|
||||||
|
fail(label, strings.concat(stage,
|
||||||
|
": destination address is not reloaded after CALL"));
|
||||||
|
};
|
||||||
|
if (!testenv.has(w, "TEXT main.probe,$16")) {
|
||||||
|
fail(label, strings.concat(stage,
|
||||||
|
": dedicated address spill is absent from the frame"));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn commandexit(dir: str, name: str, av: []str) i32 = {
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(dir, dir, name, av, tmo(), &co);
|
||||||
|
if (co.termination != exec.termination.EXIT) { return -1; };
|
||||||
|
return co.code;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn unwrapcallalign() void = {
|
||||||
|
let label: str = "global_unwrap_call_alignment";
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let src: str = strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"type e = !i32;\n",
|
||||||
|
"type pair = struct { a: i64, b: i64 };\n",
|
||||||
|
"type box = struct { f: pair, guard: i64 };\n",
|
||||||
|
"fn mkpair(x: f64) (pair | e) = {\n",
|
||||||
|
" return pair { a = 111i64, b = 222i64 };\n",
|
||||||
|
"};\n",
|
||||||
|
"let gb: box = box {\n",
|
||||||
|
" f = pair { a = 9i64, b = 9i64 }, guard = 7i64\n",
|
||||||
|
"};\n",
|
||||||
|
"fn probe() i32 = {\n",
|
||||||
|
" gb.f = mkpair(1.25f64)!;\n",
|
||||||
|
" if (gb.f.a != 111i64 || gb.f.b != 222i64",
|
||||||
|
" || gb.guard != 7i64) { return 91; };\n",
|
||||||
|
" return 37;\n",
|
||||||
|
"};\n",
|
||||||
|
"export fn main() i32 = { return probe(); };\n");
|
||||||
|
testenv.writefile(strings.concat(td, "/src.ww"), src);
|
||||||
|
emitstage(td, label, "cstage", testenv.driver("w6c"), "cs.s");
|
||||||
|
emitstage(td, label, "wwstage", testenv.driver("w6c_ww"), "ws.s");
|
||||||
|
let cs: str = testenv.readfile(strings.concat(td, "/cs.s"));
|
||||||
|
let ws: str = testenv.readfile(strings.concat(td, "/ws.s"));
|
||||||
|
unwrapaligncheck(label, "cstage", cs);
|
||||||
|
unwrapaligncheck(label, "wwstage", ws);
|
||||||
|
if (!testenv.same(cs, ws)) {
|
||||||
|
fail(label, "cstage vs wwstage asm differs");
|
||||||
|
};
|
||||||
|
let out: str = strings.concat(td, "/unwrapalign");
|
||||||
|
let bav: []str = [testenv.driver("ww"), "build", "-o", out,
|
||||||
|
"src.ww"];
|
||||||
|
if (commandexit(td, "build", bav) != 0) {
|
||||||
|
fail(label, "runtime pin build failed");
|
||||||
|
};
|
||||||
|
let rav: []str = [out];
|
||||||
|
if (commandexit(td, "run", rav) != 37) {
|
||||||
|
fail(label, "payload/neighbor runtime exit != 37");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user