w6c+selfhost+lib: cgen quality batch + lib Hare-shape graduation
Six fixes across the toolchain, surfaced by lib/lisp porting work.
1. f64 compound assigns (`acc += d`, `-=`, `*=`, `/=`). Both stages
load slot → X1, OP X0 into X1, store back (ADDSD/SUBSD/MULSD/
DIVSD are reg-reg only). Previous MOVSD-overwrite dropped the
OP. Locals and top-level lets.
2. Top-level `[N]u8` arrays + `&arr[i]`. let_emit_size grows a
TY_ARRAY branch so zero-init DATAW lands; cgindex / N_INDEX
store / `&base[i]` all detect a global array base and use
LEAQ name(SB) instead of LEAQ (BP). TK_AMP no longer pre-
evaluates the operand as a value-load — `&base[i]` computes
base + i*esz directly. Unblocks Hare's static-buffer pattern:
strconv.{u64,i64,f64}tos graduate to module-level `*_buf`
arrays and return owned views.
3. Cross-module `pkg.Enum.MEMBER`. Nested N_DOT chains that
don't fold to a known shape now emit `MOVQ <leaf>(SB), AX`
(mirrors the bare-IDENT unresolved fallback), so isolation
probes — and the test 990 cgen-match floor — stay consistent
across stages. strconv exposes `base` as a real `enum i32`;
callers updated. The `main` exemption (linker entry-point
keeps bare name even when not exported) mirrors C-side
collectmods into selfhost cgendecl.
4. Sum-typed parameter ABI. lib/bytes.{index,rindex} take
`(u8 | []u8)` needle; lib/strings.byteindex / rbyteindex take
`(str | rune)` needle (Hare-shaped; the byte-wise misnomer
`index` is dropped). tagged_arg_size cap bumps to 48 (6 int
regs), with a new partial-fit branch on the callee: when an
N-word tagged arg overflows remaining regs, fill what fits and
stitch the rest from positive BP offsets. scanlocals MCASE
handles slice binds (24B) and walks each arm with a saved /
restored seenmark set so two arms naming the same local each
get their own slot — matches cstage's per-arm scope reset.
5. 4-reg tagged-return ABI (AX=tag, DX=word0, CX=word1, R8=word2),
up from 3 regs. Slice-payload variants (`([]T | E)`, slot 32B)
round-trip ptr/len/cap end-to-end. Every receive site updates:
let-init via cgwidentaggedstore, match scrutinee spill, cgindex
tagged-element load (both N_IDENT and fallback bases),
pushargsrev tagged-ident arg (reads word count from slot size),
cgreturn slice variant in the shuffle path.
6. `expr: TaggedAlias` is a widening, not a re-interpret. C cgen +
selfhost cgwidentaggedstore peel an N_CAST whose destination IS
the union — so cgexpr's natural shape (str: AX=ptr, BX=len;
slice: AX=ptr, BX=len, CX=cap) is consumed by the matching
concrete-variant branch instead of being misread as a tagged
AX/DX/CX triple. Inner casts to a concrete variant (`7: i32`)
keep their type for proper tag lookup. `[N]Alias` arrays
resolve element size via slotsize + aliaslookup, and aliaslookup
strips a `pkg.` prefix so cross-module references work.
lib/fmt grows `formattable = (i64 | str | bool | rune)` plus
`printv` / `printlnv` taking an explicit `[]formattable` slice (the
receive side of Hare's `args: formattable...`). Call-site variadic
gather isn't wired — callers either hand-build the slice or compose
strconv.i64tos + strings.concat.
700_e2e: 114 → 123 rows (f64 compound, top-level u8 arrays + `&buf[i]`,
pkg.Enum.MEMBER, sum-typed (str|rune) and (u8|[]u8) params, 4-reg
slice-return ABI, formattable array). 26/26 tests, bootstrap stable
through ww4.
This commit is contained in:
430
cmd/w6c/cgen.c
430
cmd/w6c/cgen.c
@@ -152,7 +152,11 @@ tagged_arg_size(Type *t)
|
||||
if (t == NULL) return 0;
|
||||
if (t->kind == TY_NAMED) t = t->under;
|
||||
if (t == NULL || t->kind != TY_TAGGED) return 0;
|
||||
if (t->size > 24) return 0;
|
||||
/* Param/let/struct contexts have 6 int regs (DI..R9) so a 48B
|
||||
* tagged union (6 words) still fits in registers. Return values
|
||||
* are stricter (AX:DX:CX, max 24B) — gated separately in
|
||||
* cgreturn. */
|
||||
if (t->size > 48) return 0;
|
||||
return (int)t->size;
|
||||
}
|
||||
|
||||
@@ -444,6 +448,11 @@ let_emit_size(Type *t)
|
||||
case TY_STRUCT:
|
||||
return (int)u->size; /* zero-init only; field reads/
|
||||
* scalar-field writes only. */
|
||||
case TY_ARRAY:
|
||||
return (int)u->size; /* zero-init only; element
|
||||
* loads/stores via cgindex. Mirror
|
||||
* of selfhost letemitsize's
|
||||
* N_TARRAY branch. */
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -481,6 +490,17 @@ let_isstruct(Type *t)
|
||||
return u && u->kind == TY_STRUCT;
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a fixed-length array? Array globals are
|
||||
* zero-init DATAW slots; cgindex addresses them as LEAQ name(SB)
|
||||
* and lets the element load/store run as usual. */
|
||||
static int
|
||||
let_isarray(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
||||
return u && u->kind == TY_ARRAY;
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a float (f32 or f64)? Float globals flow
|
||||
* through X0 — load/store goes LEAQ name(SB),CX → MOVSS/MOVSD via the
|
||||
* indirect, since the asm has no D_EXTERN form for SSE moves yet. */
|
||||
@@ -848,6 +868,27 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* `expr: TaggedAlias` where the cast's destination IS the union
|
||||
* itself is a widening, not a re-interpret. cgexpr on the cast
|
||||
* leaves the inner expression's register shape (str: AX=ptr,
|
||||
* BX=len), not the tagged AX/DX/CX triple — so route through the
|
||||
* concrete-variant branches below by peeling the cast. Casts to
|
||||
* a concrete variant (`7: i32`) keep their type for proper tag
|
||||
* lookup and fall through to the matching branch. */
|
||||
if (src && src->kind == N_CAST && src->lhs) {
|
||||
Type *castt = src->type;
|
||||
Type *castu = (castt && castt->kind == TY_NAMED)
|
||||
? castt->under : castt;
|
||||
Type *innert = src->lhs->type;
|
||||
Type *innu = (innert && innert->kind == TY_NAMED)
|
||||
? innert->under : innert;
|
||||
int cast_is_widen = (castu == du) ||
|
||||
(castu && castu->kind == TY_TAGGED && type_eq(castt, dst));
|
||||
int inner_is_tagged = innu && innu->kind == TY_TAGGED;
|
||||
if (cast_is_widen && !inner_is_tagged) {
|
||||
src = src->lhs;
|
||||
}
|
||||
}
|
||||
Type *st = src ? src->type : NULL;
|
||||
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
/* Tagged → tagged subset: copy slot words then tag-remap. */
|
||||
@@ -871,6 +912,9 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
if (ssz > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, slot_off + 16));
|
||||
if (ssz > 24)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
amem(D_BP, slot_off + 24));
|
||||
}
|
||||
if (ssz < sz) {
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
@@ -965,6 +1009,19 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* Slice payload: cgexpr leaves (AX=ptr, BX=len, CX=cap). The
|
||||
* slot layout is tag@+0, ptr@+8, len@+16, cap@+24 — requires the
|
||||
* destination tagged-union slot be at least 32B. */
|
||||
if (type_isslice(st) || (su && su->kind == TY_SLICE)) {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, slot_off + 16));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, slot_off + 24));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* Scalar / pointer / etc. The high slot word (when sz > 16) is
|
||||
* left untouched here — match dispatches on the tag word first
|
||||
* and only the str branch reads slot+16, so leaving the pad
|
||||
@@ -977,9 +1034,12 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, slot_off + 0));
|
||||
}
|
||||
|
||||
/* cg_widen_tagged_push — call-site widening. Materialise the tagged
|
||||
* value in a stack scratch slot then push slot words high→low so the
|
||||
* arg-register pop drain sees tag first, then payload words. */
|
||||
/* cg_widen_tagged_push — call-site widening. For shapes where cgexpr
|
||||
* leaves the value directly in registers (str: AX=ptr, BX=len; slice:
|
||||
* AX=ptr, BX=len, CX=cap; scalar: AX), push from registers without a
|
||||
* scratch slot. Struct payload and tagged-subset re-layout still
|
||||
* route through a scratch slot. The direct-push form keeps wwstage's
|
||||
* asm byte-identical to cstage on the byteindex / index family. */
|
||||
static void
|
||||
cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
{
|
||||
@@ -990,12 +1050,51 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
return;
|
||||
}
|
||||
Type *st = src ? src->type : NULL;
|
||||
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
int src_is_struct = su && su->kind == TY_STRUCT;
|
||||
int src_is_tagged = su && su->kind == TY_TAGGED;
|
||||
if (!src_is_struct && !src_is_tagged) {
|
||||
/* Direct-push fast path: str / slice / scalar / pointer. */
|
||||
cgexpr(c, src, *locals_p);
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
if (tag < 0) tag = 0;
|
||||
if (type_isstr(st) || (su && su->kind == TY_STR)) {
|
||||
/* slot 24: [+0]=tag, [+8]=ptr, [+16]=len. Push len,
|
||||
* ptr, tag (high→low so pop drains tag first). */
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* ptr */
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* tag */
|
||||
return;
|
||||
}
|
||||
if (type_isslice(st) || (su && su->kind == TY_SLICE)) {
|
||||
/* slot 32: [+0]=tag, [+8]=ptr, [+16]=len, [+24]=cap. */
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* ptr */
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* tag */
|
||||
return;
|
||||
}
|
||||
/* Scalar / pointer variant. Pad with zero high words when
|
||||
* the slot has room for a wider variant. */
|
||||
int nwords = sz / 8;
|
||||
for (int k = nwords - 1; k >= 2; k--) {
|
||||
ins2(c, A_XORQ, areg(D_DX), areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* value at +8 */
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* tag at +0 */
|
||||
return;
|
||||
}
|
||||
const char *scr_name = mklabel(c, "argscr");
|
||||
int scr = local_alloc(c, locals_p, scr_name, sz, cg_frame);
|
||||
/* Zero the scratch slot first so any pad word the store path
|
||||
* leaves untouched (scalar variant in a >16B slot, struct payload
|
||||
* shorter than the slot's value area) reads as 0 on the callee.
|
||||
* The store path then writes the variant bytes over the zeros. */
|
||||
* leaves untouched (struct payload shorter than the slot's value
|
||||
* area) reads as 0 on the callee. The store path then writes the
|
||||
* variant bytes over the zeros. */
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, scr + k));
|
||||
@@ -1120,6 +1219,84 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
case N_UN:
|
||||
/* Address-of has its own evaluation strategy — we want the
|
||||
* address of the operand, not its value. Special-case before
|
||||
* the cgexpr pre-eval below so `&arr[i]` doesn't compile the
|
||||
* value load and then discard it. */
|
||||
if (n->op == TK_AMP) {
|
||||
Node *opnd = n->lhs;
|
||||
if (opnd && opnd->kind == N_IDENT) {
|
||||
int off = localfind(locals, opnd->str);
|
||||
if (off != 0) {
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
|
||||
} else if (let_islet(opnd->str)) {
|
||||
ins2(c, A_LEAQ, masym(c, opnd->str),
|
||||
areg(D_AX));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (opnd && opnd->kind == N_INDEX) {
|
||||
/* &base[i] = base + i*esz, no dereference. */
|
||||
Node *base = opnd->lhs;
|
||||
Node *idx = opnd->rhs;
|
||||
Type *bt = base ? base->type : NULL;
|
||||
Type *bu = (bt && bt->kind == TY_NAMED)
|
||||
? bt->under : bt;
|
||||
int esz = (bu && bu->sub)
|
||||
? (int)bu->sub->size : 1;
|
||||
if (bu && bu->kind == TY_STR) esz = 1;
|
||||
cgexpr(c, idx, locals); /* idx → AX */
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
if (base && base->kind == N_IDENT) {
|
||||
int boff = localfind(locals,
|
||||
base->str);
|
||||
int is_arr = bu &&
|
||||
bu->kind == TY_ARRAY;
|
||||
if (boff != 0) {
|
||||
if (is_arr) {
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
}
|
||||
} else if (let_islet(base->str)) {
|
||||
if (is_arr) {
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
}
|
||||
} else {
|
||||
ins2(c, A_XORQ, areg(D_BX),
|
||||
areg(D_BX));
|
||||
}
|
||||
ins2(c, A_ADDQ, areg(D_BX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
/* Complex base: eval to AX, swap into BX,
|
||||
* then add the saved scaled idx. */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, base, locals);
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_BX), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
/* Other shapes (& on a complex expr): silent drop,
|
||||
* mirrors the pre-existing fallback. */
|
||||
break;
|
||||
}
|
||||
cgexpr(c, n->lhs, locals);
|
||||
switch (n->op) {
|
||||
case TK_MINUS:
|
||||
@@ -1158,21 +1335,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
label(c, e);
|
||||
break;
|
||||
}
|
||||
case TK_AMP: {
|
||||
/* address-of for an N_IDENT: local frame slot first,
|
||||
* else a top-level mutable let (RIP-relative LEAQ).
|
||||
* Anything else (e.g. & on an undefined name) silently
|
||||
* drops, matching the pre-existing behaviour. */
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off != 0) {
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
|
||||
} else if (let_islet(n->lhs->str)) {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_AX));
|
||||
}
|
||||
}
|
||||
case TK_AMP:
|
||||
/* Handled in the pre-cgexpr early-exit above. */
|
||||
break;
|
||||
}
|
||||
case TK_STAR: /* deref */
|
||||
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
|
||||
break;
|
||||
@@ -1734,16 +1899,62 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
/* float assignment to a local or top-level global. Globals
|
||||
* route through LEAQ+indirect (no D_EXTERN SSE in w6a). */
|
||||
* route through LEAQ+indirect (no D_EXTERN SSE in w6a).
|
||||
* Compound (`acc += d` etc.) loads slot into X1, combines
|
||||
* into X1 (Plan 9 syntax: OP src, dst), stores X1 back —
|
||||
* w6a's ADDSD/SUBSD/MULSD/DIVSD are register-register only,
|
||||
* so we can't use a direct mem-form like the integer ADDQ. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && node_isfloat(n)) {
|
||||
cgexpr(c, n->rhs, locals); /* X0 */
|
||||
int op = op_for(n, A_MOVSD, A_MOVSS);
|
||||
int mvop = op_for(n, A_MOVSD, A_MOVSS);
|
||||
int addop = op_for(n, A_ADDSD, A_ADDSS);
|
||||
int subop = op_for(n, A_SUBSD, A_SUBSS);
|
||||
int mulop = op_for(n, A_MULSD, A_MULSS);
|
||||
int divop = op_for(n, A_DIVSD, A_DIVSS);
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
int isglobal = (off == 0) && let_islet(n->lhs->str);
|
||||
if (off == 0 && !isglobal) break;
|
||||
if (n->op == TK_ASSIGN) {
|
||||
if (off != 0) {
|
||||
ins2(c, mvop, areg(D_X0), amem(D_BP, off));
|
||||
} else {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
ins2(c, mvop, areg(D_X0), amem(D_CX, 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* Compound: X1 = load; X1 OP= X0; store X1. */
|
||||
int fop = -1;
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ: fop = addop; break;
|
||||
case TK_MINUSEQ: fop = subop; break;
|
||||
case TK_STAREQ: fop = mulop; break;
|
||||
case TK_SLASHEQ: fop = divop; break;
|
||||
default: break;
|
||||
}
|
||||
if (off != 0) {
|
||||
ins2(c, op, areg(D_X0), amem(D_BP, off));
|
||||
} else if (let_islet(n->lhs->str)) {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_CX));
|
||||
ins2(c, op, areg(D_X0), amem(D_CX, 0));
|
||||
if (fop < 0) {
|
||||
/* Unsupported compound (e.g., %= on float):
|
||||
* fall back to plain store of rhs. */
|
||||
ins2(c, mvop, areg(D_X0),
|
||||
amem(D_BP, off));
|
||||
break;
|
||||
}
|
||||
ins2(c, mvop, amem(D_BP, off), areg(D_X1));
|
||||
ins2(c, fop, areg(D_X0), areg(D_X1));
|
||||
ins2(c, mvop, areg(D_X1), amem(D_BP, off));
|
||||
} else {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
if (fop < 0) {
|
||||
ins2(c, mvop, areg(D_X0),
|
||||
amem(D_CX, 0));
|
||||
break;
|
||||
}
|
||||
ins2(c, mvop, amem(D_CX, 0), areg(D_X1));
|
||||
ins2(c, fop, areg(D_X0), areg(D_X1));
|
||||
ins2(c, mvop, areg(D_X1), amem(D_CX, 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1824,13 +2035,30 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* scaled idx */
|
||||
/* base address → BX */
|
||||
if (base->kind == N_IDENT && is_arr) {
|
||||
/* base address → BX. Top-level array → LEAQ
|
||||
* name(SB); top-level ptr → MOVQ name(SB); locals
|
||||
* route off BP. */
|
||||
if (base->kind == N_IDENT) {
|
||||
int off = localfind(locals, base->str);
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_BX));
|
||||
} else if (base->kind == N_IDENT) {
|
||||
int off = localfind(locals, base->str);
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
||||
int isglobal = (off == 0) &&
|
||||
let_islet(base->str);
|
||||
if (isglobal && is_arr) {
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
} else if (isglobal) {
|
||||
ins2(c, A_MOVQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
} else if (is_arr) {
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
}
|
||||
} else {
|
||||
cgexpr(c, base, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
||||
@@ -2377,7 +2605,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
continue;
|
||||
}
|
||||
if (args[i]->kind == N_SLICE) {
|
||||
if (!widen[i] && args[i]->kind == N_SLICE) {
|
||||
Node *base = args[i]->lhs;
|
||||
Node *lo = args[i]->rhs;
|
||||
Node *hi = args[i]->cond;
|
||||
@@ -2482,12 +2710,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* ptr — top */
|
||||
} else if (node_istaggedarg(args[i])) {
|
||||
/* Tagged-return ABI: AX=tag, DX=val0[, CX=val1].
|
||||
* Push high-to-low so pop drains tag first (into
|
||||
* arg-reg[0]), then values into arg-reg[1..].
|
||||
* Nullable (sz=8): AX holds the pointer, no
|
||||
* value-word registers — push just AX. */
|
||||
/* Tagged-return ABI: AX=tag, DX=val0,
|
||||
* CX=val1, R8=val2. Push high-to-low so pop
|
||||
* drains tag first (into arg-reg[0]), then
|
||||
* values into arg-reg[1..]. Nullable (sz=8):
|
||||
* AX holds the pointer, no value-word
|
||||
* registers — push just AX. */
|
||||
int sz = tagged_arg_size(args[i]->type);
|
||||
if (sz > 24)
|
||||
ins1(c, A_PUSHQ, areg(D_R8));
|
||||
if (sz > 16)
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
if (sz > 8)
|
||||
@@ -2667,9 +2898,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* Spill non-ident scrutinees (e.g. `match (foo()?)`) into
|
||||
* a scratch slot so we can index out the tag/value. The
|
||||
* call ABI for tagged returns is AX=tag, DX=value0,
|
||||
* CX=value1 — copy each word into the slot. Nullable
|
||||
* returns are single-word: AX is the pointer; spill
|
||||
* only that. */
|
||||
* CX=value1, R8=value2 — copy each word into the slot.
|
||||
* Nullable returns are single-word: AX is the pointer;
|
||||
* spill only that. */
|
||||
sl_off = localoff(c, &locals, "@match_spill", slot_size,
|
||||
cg_frame);
|
||||
cgexpr(c, s, locals);
|
||||
@@ -2680,6 +2911,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (slot_size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, sl_off + 16));
|
||||
if (slot_size > 24)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
amem(D_BP, sl_off + 24));
|
||||
}
|
||||
}
|
||||
char *end = mklabel(c, "match_end");
|
||||
@@ -2758,8 +2992,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
amem(D_BP, voff));
|
||||
}
|
||||
} else {
|
||||
int bsz = (bu && bu->kind == TY_STR)
|
||||
? 16 : 8;
|
||||
int bsz = 8;
|
||||
if (bu && bu->kind == TY_STR) bsz = 16;
|
||||
else if (bu && bu->kind == TY_SLICE) bsz = 24;
|
||||
else if (bu) bsz = (int)bu->size;
|
||||
if (bsz <= 0) bsz = 8;
|
||||
/* local_alloc to dodge name-collision
|
||||
* dedup — a 16B str bind shadowing an
|
||||
* 8B outer would otherwise overflow
|
||||
@@ -3352,6 +3589,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Nested module-qualified field where the chain didn't fold to
|
||||
* a known shape (typical when w6c runs on a single file with
|
||||
* `use mod;` but no driver concatenation — the body's enum /
|
||||
* struct hasn't been seen). Emit `MOVQ <leaf>(SB), AX` so the
|
||||
* linker surfaces a clean undefined-symbol error on the leaf
|
||||
* — mirrors the bare-N_IDENT unresolved fallback used by
|
||||
* single-segment N_DOTs. Keeps cstage / wwstage byte-aligned
|
||||
* on the cgen-match isolation probes. */
|
||||
if (n->lhs && n->lhs->kind == N_DOT && n->str) {
|
||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
/* fall through to base evaluation; result placeholder */
|
||||
cgexpr(c, n->lhs, locals);
|
||||
dot_done:
|
||||
@@ -3372,13 +3621,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
|
||||
if (n->lhs->kind == N_IDENT && u) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
int isglobal = (off == 0) && let_islet(n->lhs->str);
|
||||
cgexpr(c, n->rhs, locals); /* idx → AX */
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
||||
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
||||
}
|
||||
/* base address into BX */
|
||||
if (u->kind == TY_ARRAY) {
|
||||
/* base address into BX. Top-level array → LEAQ
|
||||
* name(SB); top-level ptr → MOVQ name(SB) (the symbol
|
||||
* holds the pointer); locals route off BP. */
|
||||
if (isglobal && u->kind == TY_ARRAY) {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
} else if (isglobal) {
|
||||
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
} else if (u->kind == TY_ARRAY) {
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_BX));
|
||||
} else {
|
||||
/* slice/str/ptr: ptr field is at off+0 */
|
||||
@@ -3394,12 +3652,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
/* tagged element: load slot words into (AX=tag,
|
||||
* DX=val0, CX=val1) — matches the tagged-return ABI
|
||||
* so let-init / match / call-arg paths consume it
|
||||
* without spilling. Nullable folded element is one
|
||||
* word in AX (caller treats it as a pointer). */
|
||||
* DX=val0, CX=val1, R8=val2) — matches the
|
||||
* tagged-return ABI so let-init / match / call-arg
|
||||
* paths consume it without spilling. Nullable folded
|
||||
* element is one word in AX (caller treats it as a
|
||||
* pointer). */
|
||||
if (elem_tagged) {
|
||||
int ssz = (int)esubu->size;
|
||||
if (ssz > 24)
|
||||
ins2(c, A_MOVQ, amem(D_BX, 24),
|
||||
areg(D_R8));
|
||||
if (ssz > 16)
|
||||
ins2(c, A_MOVQ, amem(D_BX, 16),
|
||||
areg(D_CX));
|
||||
@@ -3446,6 +3708,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (elem_tagged) {
|
||||
int ssz = (int)esubu->size;
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
||||
if (ssz > 24)
|
||||
ins2(c, A_MOVQ, amem(D_BX, 24), areg(D_R8));
|
||||
if (ssz > 16)
|
||||
ins2(c, A_MOVQ, amem(D_BX, 16), areg(D_CX));
|
||||
if (ssz > 8)
|
||||
@@ -3861,13 +4125,27 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
/* same tagged type: forward AX/DX/CX. */
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (!istagged && !isstruct) {
|
||||
/* str / scalar variant: synthesise the
|
||||
* tag in AX and shuffle the value into
|
||||
* DX[/CX]. Direct register path keeps
|
||||
* the asm short — no scratch slot. */
|
||||
/* str / slice / scalar variant: synthesise
|
||||
* the tag in AX and shuffle the value into
|
||||
* DX[/CX[/R8]]. Direct register path keeps
|
||||
* the asm short — no scratch slot.
|
||||
* Tagged-return ABI: AX=tag, DX=word0,
|
||||
* CX=word1, R8=word2. Slice payload uses
|
||||
* all four; str uses three; scalar uses
|
||||
* two. */
|
||||
int tag = cg_tag_for_variant(rt, vt);
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
if (type_isstr(vt)) {
|
||||
if (type_isslice(vt)) {
|
||||
/* cgexpr leaves (AX=ptr, BX=len,
|
||||
* CX=cap). Move into the return
|
||||
* shuffle: DX=ptr, CX=len, R8=cap. */
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
areg(D_R8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
areg(D_DX));
|
||||
} else if (type_isstr(vt)) {
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
@@ -3881,10 +4159,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
} else {
|
||||
/* Struct variant or tagged-subset:
|
||||
* materialise the widened value in a
|
||||
* scratch slot, then load AX/DX/CX
|
||||
* scratch slot, then load AX/DX/CX/R8
|
||||
* from the slot. Struct literal: field
|
||||
* stores; struct ident: word copy;
|
||||
* tagged subset: copy + tag remap. */
|
||||
* tagged subset: copy + tag remap.
|
||||
* 4th word in R8 covers slice payload
|
||||
* variants (slot >= 32B). */
|
||||
int sz = (int)rt->size;
|
||||
const char *scrn = mklabel(c, "retscr");
|
||||
int scr = local_alloc(c, locals, scrn,
|
||||
@@ -3905,6 +4185,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + 16),
|
||||
areg(D_CX));
|
||||
if (sz > 24)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + 24),
|
||||
areg(D_R8));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
@@ -4337,6 +4621,33 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
amem(D_BP, off));
|
||||
argi++;
|
||||
}
|
||||
} else if (eightbytes > 1 && regs_left > 0 &&
|
||||
(slice || is_str || is_struct || is_tagged)) {
|
||||
/* Multi-word arg that partially fits in regs: caller
|
||||
* filled (regs_left) registers greedily, the rest spilled
|
||||
* to stack at positive BP offsets. Stitch a single local
|
||||
* slot from both sources so the body sees a contiguous
|
||||
* value. Mirrors the SysV greedy reg fill the caller
|
||||
* does. */
|
||||
int sz = slice ? 24 : (is_str ? 16 :
|
||||
(is_struct ? (int)pu->size :
|
||||
(is_tagged ? tagged_sz : 8)));
|
||||
int off = localoff(c, &locals, p->str, sz, &frame);
|
||||
extern int cg_stack_arg_cursor;
|
||||
int k = 0;
|
||||
for (; k < regs_left; k++, argi++)
|
||||
ins2(c, A_MOVQ,
|
||||
areg(sysv_argregs[argi]),
|
||||
amem(D_BP, off + k * 8));
|
||||
for (; k < eightbytes; k++) {
|
||||
int stack_off = 16 +
|
||||
cg_stack_arg_cursor * 8;
|
||||
cg_stack_arg_cursor++;
|
||||
ins2(c, A_MOVQ, amem(D_BP, stack_off),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + k * 8));
|
||||
}
|
||||
} else {
|
||||
/* stack-spilled. Access in place via positive BP offset. */
|
||||
static int stack_arg_off;
|
||||
@@ -4501,11 +4812,14 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
continue;
|
||||
}
|
||||
/* Otherwise: zero-init. str accepts nil / ""; struct
|
||||
* accepts no rhs at all; slice accepts nil. */
|
||||
* accepts no rhs at all; slice accepts nil; array accepts
|
||||
* no rhs (literal-array init isn't wired). */
|
||||
if (r != NULL) {
|
||||
int is_struct = let_isstruct(d->type);
|
||||
int is_array = let_isarray(d->type);
|
||||
int empty_str = (r->kind == N_STRLIT && r->strlen == 0);
|
||||
if (is_struct) continue;
|
||||
if (is_array) continue;
|
||||
if (r->kind != N_NIL && !empty_str) continue;
|
||||
}
|
||||
emit_data_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
||||
|
||||
Reference in New Issue
Block a user