w6c+selfhost: widen concrete variant to tagged-union call arg
Tagged-union widening already fired for `let r: (str|rune) = "...";`, `r = "...";`, and `return "..."` from a tagged-returning fn — but not at call sites, so `fn f(x: (str|rune))` couldn't be called with a bare str or rune. The arg was pushed as its own static type (2 words for str, 1 for rune) while the callee's slot expected 3 (tag + payload). C cgen: at the call boundary, look up the callee's declared param type per arg. When the param is TY_TAGGED and the arg is a concrete variant, materialise (tag, value-words, padding) sized to the param's tagged_arg_size — then the existing pop-into-arg-regs logic picks it up. Nullable `(*T | void)` collapses to a single 8B push. selfhost: fnret now carries the params head alongside rtype (amalloc bumped to 48); pushargsrev takes the matching param node and runs the same widening sequence per arg. The pop drain in cgcall already handled extra slot words, so no change needed on that side. Verified with a smoke covering str/rune literals, typed locals, pre-existing tagged-local pass-through, and nullable widening from a raw pointer. Selfhost emits byte-identical asm to C cgen on the test.
This commit is contained in:
@@ -2064,6 +2064,35 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
Node *args[16] = {0};
|
Node *args[16] = {0};
|
||||||
for (Node *a = n->list; a; a = a->next)
|
for (Node *a = n->list; a; a = a->next)
|
||||||
if (argcount < 16) args[argcount++] = a;
|
if (argcount < 16) args[argcount++] = a;
|
||||||
|
/* Resolve callee fn-type so we can match each arg against
|
||||||
|
* its declared parameter type — needed to detect implicit
|
||||||
|
* widening of a concrete variant into a tagged-union slot. */
|
||||||
|
Type *callee_t = n->lhs ? n->lhs->type : NULL;
|
||||||
|
Type *cu = (callee_t && callee_t->kind == TY_NAMED) ?
|
||||||
|
callee_t->under : callee_t;
|
||||||
|
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
||||||
|
cu->params : NULL;
|
||||||
|
/* widen[i]: param is tagged, arg is a concrete variant.
|
||||||
|
* widen_sz[i]: param's tagged slot size (8/16/24).
|
||||||
|
* widen_param[i]: param type (for tag-index lookup). */
|
||||||
|
int widen[16] = {0};
|
||||||
|
int widen_sz[16] = {0};
|
||||||
|
Type *widen_param[16] = {0};
|
||||||
|
{
|
||||||
|
Tparam *p = callee_params;
|
||||||
|
for (int i = 0; i < argcount; i++) {
|
||||||
|
if (p == NULL) break;
|
||||||
|
Type *at = args[i] ? args[i]->type : NULL;
|
||||||
|
int psz = tagged_arg_size(p->type);
|
||||||
|
int arg_tagged = tagged_arg_size(at) > 0;
|
||||||
|
if (psz > 0 && !arg_tagged) {
|
||||||
|
widen[i] = 1;
|
||||||
|
widen_sz[i] = psz;
|
||||||
|
widen_param[i] = p->type;
|
||||||
|
}
|
||||||
|
p = p->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* eval right-to-left, push to stack */
|
/* eval right-to-left, push to stack */
|
||||||
for (int i = argcount - 1; i >= 0; i--) {
|
for (int i = argcount - 1; i >= 0; i--) {
|
||||||
if (node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
if (node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
||||||
@@ -2150,6 +2179,46 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (widen[i]) {
|
||||||
|
/* Concrete → tagged-union widening at the call
|
||||||
|
* site. Mirrors the let/assign/return widening:
|
||||||
|
* synthesise tag from the arg's static variant
|
||||||
|
* type, evaluate the arg, lay it out as the
|
||||||
|
* parameter's tagged slot, then push high→low so
|
||||||
|
* pop drains tag first. */
|
||||||
|
int tag = cg_tag_for_variant(widen_param[i],
|
||||||
|
args[i]->type);
|
||||||
|
if (tag < 0) tag = 0;
|
||||||
|
int sz = widen_sz[i];
|
||||||
|
if (sz == 8) {
|
||||||
|
/* Nullable fold: the pointer value IS the
|
||||||
|
* discriminator — no separate tag word. */
|
||||||
|
cgexpr(c, args[i], locals);
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cgexpr(c, args[i], locals);
|
||||||
|
if (node_isstr(args[i])) {
|
||||||
|
/* slot 24: [+0]=tag,[+8]=ptr,[+16]=len */
|
||||||
|
ins1(c, A_PUSHQ, areg(D_BX));
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
} else {
|
||||||
|
/* Scalar variant: single value word at +8.
|
||||||
|
* Pad a zero high word when the slot is 24B
|
||||||
|
* (some other variant of the union is 16B). */
|
||||||
|
if (sz > 16) {
|
||||||
|
ins2(c, A_XORQ, areg(D_DX),
|
||||||
|
areg(D_DX));
|
||||||
|
ins1(c, A_PUSHQ, areg(D_DX));
|
||||||
|
}
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
cgexpr(c, args[i], locals);
|
cgexpr(c, args[i], locals);
|
||||||
if (node_isfloat(args[i])) {
|
if (node_isfloat(args[i])) {
|
||||||
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
||||||
@@ -2179,6 +2248,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* responsible for cleaning them up after CALL. */
|
* responsible for cleaning them up after CALL. */
|
||||||
int ii = 0, fi = 0, stackslots = 0;
|
int ii = 0, fi = 0, stackslots = 0;
|
||||||
for (int i = 0; i < argcount; i++) {
|
for (int i = 0; i < argcount; i++) {
|
||||||
|
if (widen[i]) {
|
||||||
|
/* Pop widened tagged slot into arg-register
|
||||||
|
* class — sized by the parameter's tagged slot,
|
||||||
|
* not the arg's static type. */
|
||||||
|
int eb = widen_sz[i] / 8;
|
||||||
|
for (int k = 0; k < eb; k++) {
|
||||||
|
if (ii < 6)
|
||||||
|
ins1(c, A_POPQ,
|
||||||
|
areg(sysv_argregs[ii++]));
|
||||||
|
else
|
||||||
|
stackslots++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (node_isfloat(args[i])) {
|
if (node_isfloat(args[i])) {
|
||||||
if (fi < 8) {
|
if (fi < 8) {
|
||||||
ins2(c, A_MOVSD, amem(D_SP, 0),
|
ins2(c, A_MOVSD, amem(D_SP, 0),
|
||||||
@@ -2232,9 +2315,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* SysV: variadic callees require AL to hold the count of
|
/* SysV: variadic callees require AL to hold the count of
|
||||||
* XMM regs used in the variable portion. We don't pass
|
* XMM regs used in the variable portion. We don't pass
|
||||||
* floats yet, so AL=0 covers every case we emit. */
|
* floats yet, so AL=0 covers every case we emit. */
|
||||||
Type *callee_t = n->lhs ? n->lhs->type : NULL;
|
|
||||||
Type *cu = (callee_t && callee_t->kind == TY_NAMED) ?
|
|
||||||
callee_t->under : callee_t;
|
|
||||||
if (cu && cu->kind == TY_FN && cu->variadic)
|
if (cu && cu->kind == TY_FN && cu->variadic)
|
||||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||||
if (n->lhs->kind == N_IDENT) {
|
if (n->lhs->kind == N_IDENT) {
|
||||||
|
|||||||
@@ -5561,9 +5561,77 @@ use strconv;
|
|||||||
// the order on the stack so a left-to-right pop into argregs lands
|
// the order on the stack so a left-to-right pop into argregs lands
|
||||||
// (ptr, len) correctly is: PUSHQ BX (top), PUSHQ AX (above) — the
|
// (ptr, len) correctly is: PUSHQ BX (top), PUSHQ AX (above) — the
|
||||||
// pop sequence then yields AX, then BX.
|
// pop sequence then yields AX, then BX.
|
||||||
fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
//
|
||||||
|
// `param` is the corresponding declared parameter for `arg` (N_PARAM
|
||||||
|
// node from the callee's signature) or nil. When param's type is a
|
||||||
|
// tagged union and `arg`'s surface type is a concrete variant of it,
|
||||||
|
// we materialise (tag, value-words, pad) for the parameter slot before
|
||||||
|
// pushing — mirrors cmd/w6c/cgen.c's call-arg widening.
|
||||||
|
fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||||
if (arg == nil) { return 0; };
|
if (arg == nil) { return 0; };
|
||||||
let rest: i32 = pushargsrev(c, arg.next);
|
let nextparam: *node = nil;
|
||||||
|
if (param != nil) { nextparam = param.next; };
|
||||||
|
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
||||||
|
// Implicit widening from a concrete variant to a tagged-union
|
||||||
|
// parameter slot. Skips when the arg is already a tagged local
|
||||||
|
// (line 121's slice-or-tagged shortcut handles that).
|
||||||
|
let widensz: i32 = 0;
|
||||||
|
let widentag: i32 = 0;
|
||||||
|
if (param != nil) {
|
||||||
|
if (param.kind == nkind.N_PARAM) {
|
||||||
|
let ptype: *node = param.lhs;
|
||||||
|
if (istaggedtype(c, ptype)) {
|
||||||
|
let aistagged: bool = false;
|
||||||
|
if (arg.kind == nkind.N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, arg.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
aistagged = istaggedtype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!aistagged) {
|
||||||
|
widensz = slotsize(c, ptype);
|
||||||
|
let tagged: *node = resolvetagged(c, ptype);
|
||||||
|
let t: i32 = taggedvariantindex(c, tagged, arg);
|
||||||
|
if (t < 0) { t = 0; };
|
||||||
|
widentag = t;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (widensz == 8) {
|
||||||
|
// Nullable fold: pointer value IS the discriminator. No
|
||||||
|
// separate tag word.
|
||||||
|
cgexpr(c, arg);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
return rest + 1;
|
||||||
|
};
|
||||||
|
if (widensz > 0) {
|
||||||
|
cgexpr(c, arg);
|
||||||
|
if (nodeisstr(c, arg)) {
|
||||||
|
// slot 24: [+0]=tag,[+8]=ptr,[+16]=len. Push high→low
|
||||||
|
// so pop drains tag first into arg-reg[0].
|
||||||
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(widentag: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
} else {
|
||||||
|
// Scalar variant: single value word at +8. Pad a zero
|
||||||
|
// high word when slot is 24B (some other variant of
|
||||||
|
// the union is 16B-shaped).
|
||||||
|
if (widensz > 16) {
|
||||||
|
emitline("\tXORQ\tDX, DX\n");
|
||||||
|
emitline("\tPUSHQ\tDX\n");
|
||||||
|
};
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(widentag: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
};
|
||||||
|
return rest + widensz / 8;
|
||||||
|
};
|
||||||
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
|
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
|
||||||
// on the stack matching C cgen's sequence — push base, push hi,
|
// on the stack matching C cgen's sequence — push base, push hi,
|
||||||
// compute lo, pop into BX/CX, derive len/ptr, push (cap, len, ptr).
|
// compute lo, pop into BX/CX, derive len/ptr, push (cap, len, ptr).
|
||||||
@@ -8880,7 +8948,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
let nargs: i32 = pushargsrev(c, n.list);
|
// Look up the callee's declared params for tagged-union widening.
|
||||||
|
// fn-pointer calls (callee is a local) don't get widening — the
|
||||||
|
// user must build the tagged value explicitly. Matches the most
|
||||||
|
// common case (direct named calls).
|
||||||
|
let calleeparams: *node = nil;
|
||||||
|
if (callee != nil) {
|
||||||
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
|
calleeparams = fnparamslookup(c, callee.str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||||
@@ -12777,6 +12855,7 @@ fn emitdatasection(c: *cgen) void = {
|
|||||||
type fnret = struct {
|
type fnret = struct {
|
||||||
fname: str,
|
fname: str,
|
||||||
rtype: *node,
|
rtype: *node,
|
||||||
|
params: *node,
|
||||||
frnext: *fnret,
|
frnext: *fnret,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12785,9 +12864,10 @@ fn collectfnrets(c: *cgen, file: *node) void = {
|
|||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_FNDECL) {
|
if (d.kind == nkind.N_FNDECL) {
|
||||||
let f: *fnret = amalloc(c.a, 32u64): *fnret;
|
let f: *fnret = amalloc(c.a, 48u64): *fnret;
|
||||||
f.fname = d.str;
|
f.fname = d.str;
|
||||||
f.rtype = d.lhs;
|
f.rtype = d.lhs;
|
||||||
|
f.params = d.list;
|
||||||
f.frnext = c.fnrets;
|
f.frnext = c.fnrets;
|
||||||
c.fnrets = f;
|
c.fnrets = f;
|
||||||
};
|
};
|
||||||
@@ -12805,6 +12885,19 @@ fn fnretlookup(c: *cgen, name: str) *node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// fnparamslookup — head of the declared param-list for a fn, or nil
|
||||||
|
// if the name isn't a registered fn. Used by cgcall / pushargsrev to
|
||||||
|
// detect implicit widening from a concrete variant into a tagged-union
|
||||||
|
// parameter slot.
|
||||||
|
fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||||
|
let f: *fnret = c.fnrets;
|
||||||
|
for (f != nil) {
|
||||||
|
if (streq(f.fname, name)) { return f.params; };
|
||||||
|
f = f.frnext;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
// ---- def-constant registry ------------------------------------------
|
// ---- def-constant registry ------------------------------------------
|
||||||
//
|
//
|
||||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||||
|
|||||||
@@ -1315,6 +1315,7 @@ fn emitdatasection(c: *cgen) void = {
|
|||||||
type fnret = struct {
|
type fnret = struct {
|
||||||
fname: str,
|
fname: str,
|
||||||
rtype: *node,
|
rtype: *node,
|
||||||
|
params: *node,
|
||||||
frnext: *fnret,
|
frnext: *fnret,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1323,9 +1324,10 @@ fn collectfnrets(c: *cgen, file: *node) void = {
|
|||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_FNDECL) {
|
if (d.kind == nkind.N_FNDECL) {
|
||||||
let f: *fnret = amalloc(c.a, 32u64): *fnret;
|
let f: *fnret = amalloc(c.a, 48u64): *fnret;
|
||||||
f.fname = d.str;
|
f.fname = d.str;
|
||||||
f.rtype = d.lhs;
|
f.rtype = d.lhs;
|
||||||
|
f.params = d.list;
|
||||||
f.frnext = c.fnrets;
|
f.frnext = c.fnrets;
|
||||||
c.fnrets = f;
|
c.fnrets = f;
|
||||||
};
|
};
|
||||||
@@ -1343,6 +1345,19 @@ fn fnretlookup(c: *cgen, name: str) *node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// fnparamslookup — head of the declared param-list for a fn, or nil
|
||||||
|
// if the name isn't a registered fn. Used by cgcall / pushargsrev to
|
||||||
|
// detect implicit widening from a concrete variant into a tagged-union
|
||||||
|
// parameter slot.
|
||||||
|
fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||||
|
let f: *fnret = c.fnrets;
|
||||||
|
for (f != nil) {
|
||||||
|
if (streq(f.fname, name)) { return f.params; };
|
||||||
|
f = f.frnext;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
// ---- def-constant registry ------------------------------------------
|
// ---- def-constant registry ------------------------------------------
|
||||||
//
|
//
|
||||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||||
|
|||||||
@@ -1961,7 +1961,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
let nargs: i32 = pushargsrev(c, n.list);
|
// Look up the callee's declared params for tagged-union widening.
|
||||||
|
// fn-pointer calls (callee is a local) don't get widening — the
|
||||||
|
// user must build the tagged value explicitly. Matches the most
|
||||||
|
// common case (direct named calls).
|
||||||
|
let calleeparams: *node = nil;
|
||||||
|
if (callee != nil) {
|
||||||
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
|
calleeparams = fnparamslookup(c, callee.str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||||
|
|||||||
@@ -27,9 +27,77 @@ use strconv;
|
|||||||
// the order on the stack so a left-to-right pop into argregs lands
|
// the order on the stack so a left-to-right pop into argregs lands
|
||||||
// (ptr, len) correctly is: PUSHQ BX (top), PUSHQ AX (above) — the
|
// (ptr, len) correctly is: PUSHQ BX (top), PUSHQ AX (above) — the
|
||||||
// pop sequence then yields AX, then BX.
|
// pop sequence then yields AX, then BX.
|
||||||
fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
//
|
||||||
|
// `param` is the corresponding declared parameter for `arg` (N_PARAM
|
||||||
|
// node from the callee's signature) or nil. When param's type is a
|
||||||
|
// tagged union and `arg`'s surface type is a concrete variant of it,
|
||||||
|
// we materialise (tag, value-words, pad) for the parameter slot before
|
||||||
|
// pushing — mirrors cmd/w6c/cgen.c's call-arg widening.
|
||||||
|
fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||||
if (arg == nil) { return 0; };
|
if (arg == nil) { return 0; };
|
||||||
let rest: i32 = pushargsrev(c, arg.next);
|
let nextparam: *node = nil;
|
||||||
|
if (param != nil) { nextparam = param.next; };
|
||||||
|
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
||||||
|
// Implicit widening from a concrete variant to a tagged-union
|
||||||
|
// parameter slot. Skips when the arg is already a tagged local
|
||||||
|
// (line 121's slice-or-tagged shortcut handles that).
|
||||||
|
let widensz: i32 = 0;
|
||||||
|
let widentag: i32 = 0;
|
||||||
|
if (param != nil) {
|
||||||
|
if (param.kind == nkind.N_PARAM) {
|
||||||
|
let ptype: *node = param.lhs;
|
||||||
|
if (istaggedtype(c, ptype)) {
|
||||||
|
let aistagged: bool = false;
|
||||||
|
if (arg.kind == nkind.N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, arg.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
aistagged = istaggedtype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!aistagged) {
|
||||||
|
widensz = slotsize(c, ptype);
|
||||||
|
let tagged: *node = resolvetagged(c, ptype);
|
||||||
|
let t: i32 = taggedvariantindex(c, tagged, arg);
|
||||||
|
if (t < 0) { t = 0; };
|
||||||
|
widentag = t;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (widensz == 8) {
|
||||||
|
// Nullable fold: pointer value IS the discriminator. No
|
||||||
|
// separate tag word.
|
||||||
|
cgexpr(c, arg);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
return rest + 1;
|
||||||
|
};
|
||||||
|
if (widensz > 0) {
|
||||||
|
cgexpr(c, arg);
|
||||||
|
if (nodeisstr(c, arg)) {
|
||||||
|
// slot 24: [+0]=tag,[+8]=ptr,[+16]=len. Push high→low
|
||||||
|
// so pop drains tag first into arg-reg[0].
|
||||||
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(widentag: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
} else {
|
||||||
|
// Scalar variant: single value word at +8. Pad a zero
|
||||||
|
// high word when slot is 24B (some other variant of
|
||||||
|
// the union is 16B-shaped).
|
||||||
|
if (widensz > 16) {
|
||||||
|
emitline("\tXORQ\tDX, DX\n");
|
||||||
|
emitline("\tPUSHQ\tDX\n");
|
||||||
|
};
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(widentag: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
};
|
||||||
|
return rest + widensz / 8;
|
||||||
|
};
|
||||||
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
|
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
|
||||||
// on the stack matching C cgen's sequence — push base, push hi,
|
// on the stack matching C cgen's sequence — push base, push hi,
|
||||||
// compute lo, pop into BX/CX, derive len/ptr, push (cap, len, ptr).
|
// compute lo, pop into BX/CX, derive len/ptr, push (cap, len, ptr).
|
||||||
|
|||||||
@@ -5561,9 +5561,77 @@ use strconv;
|
|||||||
// the order on the stack so a left-to-right pop into argregs lands
|
// the order on the stack so a left-to-right pop into argregs lands
|
||||||
// (ptr, len) correctly is: PUSHQ BX (top), PUSHQ AX (above) — the
|
// (ptr, len) correctly is: PUSHQ BX (top), PUSHQ AX (above) — the
|
||||||
// pop sequence then yields AX, then BX.
|
// pop sequence then yields AX, then BX.
|
||||||
fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
//
|
||||||
|
// `param` is the corresponding declared parameter for `arg` (N_PARAM
|
||||||
|
// node from the callee's signature) or nil. When param's type is a
|
||||||
|
// tagged union and `arg`'s surface type is a concrete variant of it,
|
||||||
|
// we materialise (tag, value-words, pad) for the parameter slot before
|
||||||
|
// pushing — mirrors cmd/w6c/cgen.c's call-arg widening.
|
||||||
|
fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||||
if (arg == nil) { return 0; };
|
if (arg == nil) { return 0; };
|
||||||
let rest: i32 = pushargsrev(c, arg.next);
|
let nextparam: *node = nil;
|
||||||
|
if (param != nil) { nextparam = param.next; };
|
||||||
|
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
||||||
|
// Implicit widening from a concrete variant to a tagged-union
|
||||||
|
// parameter slot. Skips when the arg is already a tagged local
|
||||||
|
// (line 121's slice-or-tagged shortcut handles that).
|
||||||
|
let widensz: i32 = 0;
|
||||||
|
let widentag: i32 = 0;
|
||||||
|
if (param != nil) {
|
||||||
|
if (param.kind == nkind.N_PARAM) {
|
||||||
|
let ptype: *node = param.lhs;
|
||||||
|
if (istaggedtype(c, ptype)) {
|
||||||
|
let aistagged: bool = false;
|
||||||
|
if (arg.kind == nkind.N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, arg.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
aistagged = istaggedtype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!aistagged) {
|
||||||
|
widensz = slotsize(c, ptype);
|
||||||
|
let tagged: *node = resolvetagged(c, ptype);
|
||||||
|
let t: i32 = taggedvariantindex(c, tagged, arg);
|
||||||
|
if (t < 0) { t = 0; };
|
||||||
|
widentag = t;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (widensz == 8) {
|
||||||
|
// Nullable fold: pointer value IS the discriminator. No
|
||||||
|
// separate tag word.
|
||||||
|
cgexpr(c, arg);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
return rest + 1;
|
||||||
|
};
|
||||||
|
if (widensz > 0) {
|
||||||
|
cgexpr(c, arg);
|
||||||
|
if (nodeisstr(c, arg)) {
|
||||||
|
// slot 24: [+0]=tag,[+8]=ptr,[+16]=len. Push high→low
|
||||||
|
// so pop drains tag first into arg-reg[0].
|
||||||
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(widentag: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
} else {
|
||||||
|
// Scalar variant: single value word at +8. Pad a zero
|
||||||
|
// high word when slot is 24B (some other variant of
|
||||||
|
// the union is 16B-shaped).
|
||||||
|
if (widensz > 16) {
|
||||||
|
emitline("\tXORQ\tDX, DX\n");
|
||||||
|
emitline("\tPUSHQ\tDX\n");
|
||||||
|
};
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(widentag: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
};
|
||||||
|
return rest + widensz / 8;
|
||||||
|
};
|
||||||
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
|
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
|
||||||
// on the stack matching C cgen's sequence — push base, push hi,
|
// on the stack matching C cgen's sequence — push base, push hi,
|
||||||
// compute lo, pop into BX/CX, derive len/ptr, push (cap, len, ptr).
|
// compute lo, pop into BX/CX, derive len/ptr, push (cap, len, ptr).
|
||||||
@@ -8880,7 +8948,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
let nargs: i32 = pushargsrev(c, n.list);
|
// Look up the callee's declared params for tagged-union widening.
|
||||||
|
// fn-pointer calls (callee is a local) don't get widening — the
|
||||||
|
// user must build the tagged value explicitly. Matches the most
|
||||||
|
// common case (direct named calls).
|
||||||
|
let calleeparams: *node = nil;
|
||||||
|
if (callee != nil) {
|
||||||
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
|
calleeparams = fnparamslookup(c, callee.str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||||
@@ -12777,6 +12855,7 @@ fn emitdatasection(c: *cgen) void = {
|
|||||||
type fnret = struct {
|
type fnret = struct {
|
||||||
fname: str,
|
fname: str,
|
||||||
rtype: *node,
|
rtype: *node,
|
||||||
|
params: *node,
|
||||||
frnext: *fnret,
|
frnext: *fnret,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12785,9 +12864,10 @@ fn collectfnrets(c: *cgen, file: *node) void = {
|
|||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_FNDECL) {
|
if (d.kind == nkind.N_FNDECL) {
|
||||||
let f: *fnret = amalloc(c.a, 32u64): *fnret;
|
let f: *fnret = amalloc(c.a, 48u64): *fnret;
|
||||||
f.fname = d.str;
|
f.fname = d.str;
|
||||||
f.rtype = d.lhs;
|
f.rtype = d.lhs;
|
||||||
|
f.params = d.list;
|
||||||
f.frnext = c.fnrets;
|
f.frnext = c.fnrets;
|
||||||
c.fnrets = f;
|
c.fnrets = f;
|
||||||
};
|
};
|
||||||
@@ -12805,6 +12885,19 @@ fn fnretlookup(c: *cgen, name: str) *node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// fnparamslookup — head of the declared param-list for a fn, or nil
|
||||||
|
// if the name isn't a registered fn. Used by cgcall / pushargsrev to
|
||||||
|
// detect implicit widening from a concrete variant into a tagged-union
|
||||||
|
// parameter slot.
|
||||||
|
fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||||
|
let f: *fnret = c.fnrets;
|
||||||
|
for (f != nil) {
|
||||||
|
if (streq(f.fname, name)) { return f.params; };
|
||||||
|
f = f.frnext;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
// ---- def-constant registry ------------------------------------------
|
// ---- def-constant registry ------------------------------------------
|
||||||
//
|
//
|
||||||
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
||||||
|
|||||||
Reference in New Issue
Block a user