diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 51c86bb6..3082b2ab 100644 --- a/cmd/w6c/cgen.c +++ b/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 (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); diff --git a/examples/lisp/CLAUDE.md b/examples/lisp/CLAUDE.md index ddb43aa2..84bfd49f 100644 --- a/examples/lisp/CLAUDE.md +++ b/examples/lisp/CLAUDE.md @@ -61,11 +61,12 @@ the shape so a regression is easy to recognise. emit both halves of the str. `vstr` still does manual `p.text = s` (semantically equivalent, no longer required). -6. **`(slice | E)` tagged-union returns drop `slice.len`.** Still - present. The wwstage return convention is AX=tag, DX=payload1, - CX=payload2 — a 24-byte slice header doesn't fit. Inline the - slice-building loop into the caller. See `eval`'s argument-eval - inline. +6. **(retired) `(slice | E)` tagged-union returns drop `slice.len`.** + The return ABI now uses 4 regs (AX=tag, DX=ptr, CX=len, R8=cap), + covering slice-payload variants up to 32B. The inline args-eval + in `eval` is still there but no longer required — `apply` could + factor it back out into a `(slice | rterror)` helper. Left as a + readability cleanup; not a correctness fix. 7. **(retired) `xs[i].kind` drops the trailing field load.** cgdot now handles N_INDEX bases. Builtins are back to diff --git a/examples/lisp/lisp_test.ww b/examples/lisp/lisp_test.ww index 86bca45c..35a41f11 100644 --- a/examples/lisp/lisp_test.ww +++ b/examples/lisp/lisp_test.ww @@ -50,7 +50,7 @@ fn faili(name: str, why: str, got: i64) void = { os.write(2, ": ".ptr, 2u64); os.write(2, why.ptr, why.len: u64); os.write(2, " got=".ptr, 5u64); - let s: str = strconv.i64tos(got, strconv.DEC); + let s: str = strconv.i64tos(got, strconv.base.DEC); os.write(2, s.ptr, s.len: u64); os.write(2, "\n".ptr, 1u64); nfail += 1; @@ -396,7 +396,7 @@ export fn main() i32 = { // ---- summary ---- if (nfail == 0) { os.write(1, "\nlisp_test: ".ptr, 12u64); - let ts: str = strconv.i64tos(ntotal: i64, strconv.DEC); + let ts: str = strconv.i64tos(ntotal: i64, strconv.base.DEC); os.write(1, ts.ptr, ts.len: u64); os.write(1, "/".ptr, 1u64); os.write(1, ts.ptr, ts.len: u64); @@ -404,10 +404,10 @@ export fn main() i32 = { return 0; }; os.write(2, "\nlisp_test: ".ptr, 12u64); - let fs: str = strconv.i64tos(nfail: i64, strconv.DEC); + let fs: str = strconv.i64tos(nfail: i64, strconv.base.DEC); os.write(2, fs.ptr, fs.len: u64); os.write(2, " of ".ptr, 4u64); - let ts: str = strconv.i64tos(ntotal: i64, strconv.DEC); + let ts: str = strconv.i64tos(ntotal: i64, strconv.base.DEC); os.write(2, ts.ptr, ts.len: u64); os.write(2, " failed\n".ptr, 8u64); return 1; diff --git a/examples/lisp/lispcore.ww b/examples/lisp/lispcore.ww index 55dc7037..a9b6ae65 100644 --- a/examples/lisp/lispcore.ww +++ b/examples/lisp/lispcore.ww @@ -655,7 +655,7 @@ fn next(L: *lexer) (i32 | parserr | eof) = { // list parse_expr will reject it; here we just tag it. if (a.len == 1 && a[0] == '.': u8) { L.curkind = tkind.DOT; return 0; }; if (allnum(a)) { - let r = strconv.stoi64(a, strconv.DEC); + let r = strconv.stoi64(a, strconv.base.DEC); match (r) { case let v: i64 => { L.curkind = tkind.INT; @@ -1491,7 +1491,7 @@ fn obuf_puts(s: str) void = { }; fn obuf_putint(v: i64) void = { - let s: str = strconv.i64tos(v, strconv.DEC); + let s: str = strconv.i64tos(v, strconv.base.DEC); let i: i32 = 0; for (i < s.len) { obuf_putc(s.ptr[i]); i += 1; }; }; diff --git a/lib/CLAUDE.md b/lib/CLAUDE.md index b031e3a1..3bd3cf89 100644 --- a/lib/CLAUDE.md +++ b/lib/CLAUDE.md @@ -13,29 +13,38 @@ Signatures mirror Hare too, modulo: - Tagged-union returns are spelled with the ww `!` error tag where Hare uses `!void` / `!T`, and indices use the underlying length type (`i32` today, since `str.len: i32`). Example: - `strconv.stoi64(s: str, b: i32) (i64 | invalid | overflow)` — - same shape as Hare's; the base parameter is plain `i32` rather - than a `base` enum because cross-module `mod.enumtype.VALUE` - chains miscompile in the cstage cgen. `strconv` exports - `def DEC: i32 = 10;` etc. so callers say `strconv.DEC` and the - Sdef path lowers to an immediate. + `strconv.stoi64(s: str, b: strconv.base) (i64 | invalid | overflow)` + — same shape as Hare's. The base parameter is the named enum + `strconv.base` (Hare uses `enum uint`; we pick `enum i32` to + match the index type). -- Owned-`str` returns. Where Hare returns `const str` into a - thread-local static buffer (`strconv.i64tos`, `strings.dup`, - `strings.concat`), ww allocates per call and the caller frees - via `os.free(r.ptr, r.len: u64)`. Mutating a module-level `*u8` - doesn't yet round-trip through the wwstage cgen, so the static- - buffer shape isn't expressible today. +- Static-buffer `str` returns where Hare uses them. `strconv.*tos` + returns a `const str` view into a module-level buffer that is + overwritten on the next call to the same function. Callers that + need the bytes to outlive the next call duplicate via + [[strings.dup]]. Functions that genuinely allocate a fresh + buffer (`strings.dup`, `strings.concat`) still return an owned + `str` that callers free via `os.free(r.ptr, r.len: u64)`. -- Variadic ABI doesn't land yet, so callers that Hare writes as - `fmt::println(42)` are spelled `fmt.println(strconv.i64tos(42, - strconv.DEC))` for now. `lib/fmt` is intentionally print-string- - only — no `printf`-family. +- Call-site variadic sugar (`fmt::println(42)`) doesn't land yet. + The receive side does — `fmt.formattable` is a tagged union of + the printable scalar types, and `fmt.printv` / `fmt.printlnv` + take an explicit `[]formattable` slice. Until the call-site + gather is implemented, callers either hand-build the slice: + let args: [2]fmt.formattable; + args[0] = "count: ": fmt.formattable; + args[1] = 42i64: fmt.formattable; + fmt.printlnv(args[0:2]); + or compose to a single str via strconv.i64tos + strings.concat: + fmt.println(strconv.i64tos(42, strconv.base.DEC)); + `lib/fmt` is intentionally print-string-only — no `printf`-family. -- `(str | rune)`-style sum-typed parameters are split into typed - pairs (`strings.indexbyte` for the byte case, `strings.index` for - the slice case, etc.). The Hare public name `byteindex` will come - back once the sum-typed parameter ABI lands. +- `(T | U)` sum-typed parameters dispatch via `match` inside the + callee. `strings.byteindex(haystack: str, needle: (str | rune))`, + `bytes.index(s: []u8, needle: (u8 | []u8))`, and `rbyteindex`/ + `rindex` follow Hare's shape directly. The rune-indexed + `strings.index` (rune-wise position) isn't shipped yet — we don't + have UTF-8 rune iteration in the language stack. Don't ship a richer surface than Hare has. A documented subset is fine; an extension, rename, or convenience-wrapper is not — callers diff --git a/lib/bytes/bytes.ww b/lib/bytes/bytes.ww index 3c1755a3..608ef83d 100644 --- a/lib/bytes/bytes.ww +++ b/lib/bytes/bytes.ww @@ -10,64 +10,68 @@ export fn equal(a: []u8, b: []u8) bool = { return i == b.len; }; -// indexbyte — first index of byte `c` in `s`. Hare-shaped optional: -// (i32 | void). void variant indicates "not found". -export fn indexbyte(s: []u8, c: u8) (i32 | void) = { - let i: i32 = 0; - for (i < s.len) { - if (s[i] == c) { return i; }; - i += 1; - }; - return; -}; - -// rindexbyte — last index of byte `c` in `s`. Mirrors Hare's -// bytes::rindex for the byte case. -export fn rindexbyte(s: []u8, c: u8) (i32 | void) = { - let i: i32 = s.len - 1; - for (i >= 0) { - if (s[i] == c) { return i; }; - i -= 1; - }; - return; -}; - -// index — first index of `sub` in `s`. Mirrors Hare's bytes::index -// (the []u8 needle variant; the u8 needle stays as indexbyte until we -// have union-arg dispatch). Empty `sub` matches at 0. -export fn index(s: []u8, sub: []u8) (i32 | void) = { - if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return; }; - let last: i32 = s.len - sub.len; - let i: i32 = 0; - for (i <= last) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// index — first index of `needle` in `s`. Mirrors Hare's bytes::index: +// `u8` needle scans for the byte, `[]u8` needle scans for the +// substring. Returns void if absent. +export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = { + match (needle) { + case let c: u8 => { + let i: i32 = 0; + for (i < s.len) { + if (s[i] == c) { return i; }; + i += 1; }; - if (ok) { return i; }; - i += 1; + return; + }; + case let sub: []u8 => { + if (sub.len == 0) { return 0; }; + if (sub.len > s.len) { return; }; + let last: i32 = s.len - sub.len; + let i: i32 = 0; + for (i <= last) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i += 1; + }; + return; + }; }; return; }; -// rindex — last index of `sub` in `s`. Mirrors Hare's bytes::rindex -// for the slice case. -export fn rindex(s: []u8, sub: []u8) (i32 | void) = { - if (sub.len == 0) { return s.len; }; - if (sub.len > s.len) { return; }; - let i: i32 = s.len - sub.len; - for (i >= 0) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// rindex — last index of `needle` in `s`. Mirrors Hare's bytes::rindex. +// Empty []u8 needle matches at s.len. +export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = { + match (needle) { + case let c: u8 => { + let i: i32 = s.len - 1; + for (i >= 0) { + if (s[i] == c) { return i; }; + i -= 1; }; - if (ok) { return i; }; - i -= 1; + return; + }; + case let sub: []u8 => { + if (sub.len == 0) { return s.len; }; + if (sub.len > s.len) { return; }; + let i: i32 = s.len - sub.len; + for (i >= 0) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i -= 1; + }; + return; + }; }; return; }; diff --git a/lib/fmt/fmt.ww b/lib/fmt/fmt.ww index 2782833f..384d7555 100644 --- a/lib/fmt/fmt.ww +++ b/lib/fmt/fmt.ww @@ -1,16 +1,82 @@ -// fmt — minimal formatting writers. All output goes through os.write -// to a file descriptor. No printf-family yet — we don't have varargs -// in the language proper — so callers compose with strconv.i64tos / -// strings.concat to build the message and then call print / println. -// Hare's `fmt::println(42)` becomes `fmt.println(strconv.i64tos(42, -// strconv.DEC))`. +// fmt — formatting writers. Goes through os.write to a file +// descriptor. Hare's variadic call-site sugar (`fmt::println(42)` +// gathering args into a `[]formattable`) isn't wired yet; until then, +// callers either: +// +// 1. Hand-build the slice: +// let args: [2]formattable; +// args[0] = 42i64: formattable; +// args[1] = " hi": formattable; +// fmt.print(args[0:2]); +// +// 2. Compose a single str via strconv.i64tos / strings.concat: +// fmt.println(strconv.i64tos(42, strconv.base.DEC)); +// +// `print(s: str)` keeps the single-string form for the common case. use os; +use strconv; +use strings; +// formattable — tagged union of types fmt can render. Mirrors +// Hare's `fmt::formattable = (...types::numeric | uintptr | str | +// rune | bool | nullable *opaque | void)`, narrowed to the set ww +// actually has codegen for. Slot size is 24B (8 tag + 16 str +// payload). +export type formattable = (i64 | str | bool | rune); + +// vprint — write the formatted form of each element of `args` to +// `fd`. Returns total bytes written or the first negative os.write +// result. +fn vprint(fd: i32, args: []formattable) i64 = { + let total: i64 = 0; + let i: i32 = 0; + for (i < args.len) { + match (args[i]) { + case let n: i64 => { + let s: str = strconv.i64tos(n, strconv.base.DEC); + let r: i64 = os.write(fd, s.ptr, s.len: u64); + if (r < 0) { return r; }; + total += r; + }; + case let s: str => { + let r: i64 = os.write(fd, s.ptr, s.len: u64); + if (r < 0) { return r; }; + total += r; + }; + case let b: bool => { + let s: str = "false"; + if (b) { s = "true"; }; + let r: i64 = os.write(fd, s.ptr, s.len: u64); + if (r < 0) { return r; }; + total += r; + }; + case let r: rune => { + let buf: [4]u8; + buf[0] = r: u8; + let n: i64 = os.write(fd, &buf[0], 1u64); + if (n < 0) { return n; }; + total += n; + }; + }; + i += 1; + }; + return total; +}; + +// print(s: str) — single-string form for the common case. The +// variadic-style `print(args: []formattable)` lives as `printv` +// until call-site sugar lands. export fn print(s: str) i64 = { return os.write(1, s.ptr, s.len: u64); }; +// printv — Hare-shaped `print(args: formattable...)` modulo the +// call-site sugar. Callers pass an explicit `[]formattable` slice. +export fn printv(args: []formattable) i64 = { + return vprint(1, args); +}; + export fn println(s: str) i64 = { let n: i64 = os.write(1, s.ptr, s.len: u64); if (n < 0) { return n; }; @@ -19,6 +85,15 @@ export fn println(s: str) i64 = { return n + m; }; +// printlnv — like printv but adds a trailing newline. +export fn printlnv(args: []formattable) i64 = { + let n: i64 = vprint(1, args); + if (n < 0) { return n; }; + let m: i64 = os.write(1, "\n".ptr, 1u64); + if (m < 0) { return m; }; + return n + m; +}; + // errorln — write a message to stderr with a trailing newline. export fn errorln(s: str) i64 = { let n: i64 = os.write(2, s.ptr, s.len: u64); diff --git a/lib/strconv/strconv.ww b/lib/strconv/strconv.ww index abdeaffb..db36b262 100644 --- a/lib/strconv/strconv.ww +++ b/lib/strconv/strconv.ww @@ -1,11 +1,10 @@ // strconv — number↔string conversions. // -// Mirrors Hare's strconv:: surface. The *tos functions return a fresh -// owned `str`; release via os.free(r.ptr, r.len: u64) when done. -// Hare returns `const str` into a static buffer; ww allocates per -// call because the wwstage cgen doesn't currently support mutating a -// module-level `*u8` (so a lazy-init shared buffer isn't expressible -// today). Graduate to the static-buffer shape once that lands. +// Mirrors Hare's strconv:: surface. The *tos functions return a +// `const str` view into a module-level buffer that is overwritten on +// the next call to the same function; callers must copy the bytes if +// they need to outlive the next invocation. See [[strings.dup]] to +// duplicate. Matches Hare's strconv::*tos semantics. use os; use strings; @@ -22,43 +21,44 @@ export type overflow = !void; // error — any error from a strconv call. Mirrors Hare's strconv::error. export type error = !(invalid | overflow); -// base — numeric base for parsing/formatting. Plain i32 (not a named -// enum) because cross-module `strconv.base.DEC` chains miscompile in -// the cstage cgen — it emits a memory load through `base(SB)` rather -// than inlining the enum value. Hare names them as `strconv::base` -// enum values; we expose them as module-level `def`s so callers say -// `strconv.DEC` and the cgen inlines the immediate. +// base — numeric base for parsing/formatting. Mirrors Hare's +// `strconv::base` (Hare uses `enum uint`; we pick `enum i32` since +// the underlying parse/format loops index with i32). // -// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces -// lowercase a-f digits. -export def DEFAULT: i32 = 0; -export def BIN: i32 = 2; -export def OCT: i32 = 8; -export def DEC: i32 = 10; -export def HEX_UPPER: i32 = 16; -export def HEX: i32 = 16; -export def HEX_LOWER: i32 = 17; +// HEX is an alias for HEX_UPPER; HEX_LOWER is a pseudo-base that +// produces lowercase a-f digits. +export type base = enum i32 { + DEFAULT = 0, + BIN = 2, + OCT = 8, + DEC = 10, + HEX_UPPER = 16, + HEX = 16, + HEX_LOWER = 17, +}; -fn basenum(b: i32) i64 = { - if (b == BIN) { return 2; }; - if (b == OCT) { return 8; }; - if (b == HEX) { return 16; }; - if (b == HEX_UPPER) { return 16; }; - if (b == HEX_LOWER) { return 16; }; +fn basenum(b: base) i64 = { + if (b == base.BIN) { return 2; }; + if (b == base.OCT) { return 8; }; + if (b == base.HEX) { return 16; }; + if (b == base.HEX_UPPER) { return 16; }; + if (b == base.HEX_LOWER) { return 16; }; return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: i32) u8 = { +fn basedigit(d: i64, b: base) u8 = { if (d < 10) { return (d + 48): u8; }; let off: i64 = d - 10; - if (b == HEX_LOWER) { return (off + 97): u8; }; + if (b == base.HEX_LOWER) { return (off + 97): u8; }; return (off + 65): u8; }; -// u64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free(r.ptr, r.len: u64). Mirrors Hare's -// strconv::u64tos (Hare returns const str into a static buffer). -export fn u64tos(v: u64, b: i32) str = { +// u64tos — convert v to a base-b numeric string. Returns a view into +// `u64tos_buf` which is overwritten on the next call. Matches Hare's +// strconv::u64tos. +let u64tos_buf: [65]u8; + +export fn u64tos(v: u64, b: base) str = { let nb: u64 = basenum(b): u64; let tmp: [65]u8; let i: i32 = 0; @@ -70,22 +70,25 @@ export fn u64tos(v: u64, b: i32) str = { n = n / nb; i += 1; }; - let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; - buf[out] = tmp[i]; + u64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &u64tos_buf[0]; r.len = out; return r; }; -// i64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free. Mirrors Hare's strconv::i64tos. -export fn i64tos(v: i64, b: i32) str = { +// i64tos — convert v to a base-b numeric string. Returns a view into +// `i64tos_buf` which is overwritten on the next call. Independent +// buffer from u64tos so i64tos's own call to u64tos doesn't clobber +// the in-flight result. Matches Hare's strconv::i64tos. +let i64tos_buf: [66]u8; + +export fn i64tos(v: i64, b: base) str = { let neg: bool = false; let n: i64 = v; if (n < 0) { neg = true; n = -n; }; @@ -99,37 +102,33 @@ export fn i64tos(v: i64, b: i32) str = { n = n / nb; i += 1; }; - let extra: i32 = 0; - if (neg) { extra = 1; }; - let total: i32 = i + extra; - let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { buf[out] = 45u8; out += 1; }; // '-' + if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; - buf[out] = tmp[i]; + i64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &i64tos_buf[0]; r.len = out; return r; }; -export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; -export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; -export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; +export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; -export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; -export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; -export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; +export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; // digval — value of digit byte `c` under base `b`, or -1 if not a // valid digit. Letters are accepted case-insensitively under HEX / // HEX_UPPER; only lowercase under HEX_LOWER. -fn digval(c: u8, b: i32) i32 = { +fn digval(c: u8, b: base) i32 = { if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; - if (b == HEX_LOWER) { + if (b == base.HEX_LOWER) { if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; return -1; }; @@ -142,7 +141,7 @@ fn digval(c: u8, b: i32) i32 = { // No locale, no whitespace, no underscores: optional leading '-' then // digits. Returns invalid with the offending index or overflow on // out-of-range. -export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { +export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; @@ -163,7 +162,7 @@ export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { }; // stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. -export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { +export fn stou64(s: str, b: base) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let nb: u64 = basenum(b): u64; let v: u64 = 0u64; @@ -179,7 +178,7 @@ export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { return v; }; -export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { +export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -193,7 +192,7 @@ export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { return 0: invalid; // unreachable; appeases the path-cov checker }; -export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { +export fn stoi16(s: str, b: base) (i16 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -207,7 +206,7 @@ export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { return 0: invalid; }; -export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { +export fn stoi8(s: str, b: base) (i8 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -221,7 +220,7 @@ export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { return 0: invalid; }; -export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { +export fn stou32(s: str, b: base) (u32 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -234,7 +233,7 @@ export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { return 0: invalid; }; -export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { +export fn stou16(s: str, b: base) (u16 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -247,7 +246,7 @@ export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { return 0: invalid; }; -export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { +export fn stou8(s: str, b: base) (u8 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -283,13 +282,14 @@ export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. +let f64tos_buf: [64]u8; + export fn f64tos(v: f64) str = { - let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - tmp[out] = 45u8; // '-' + f64tos_buf[out] = 45u8; // '-' out += 1; f = -f; }; @@ -299,12 +299,9 @@ export fn f64tos(v: f64) str = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; + for (k < s.len) { f64tos_buf[out] = s[k]; out += 1; k += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -323,32 +320,27 @@ export fn f64tos(v: f64) str = { ip += 1; fp = 0; }; - let intstr: str = i64tos(ip, DEC); + let intstr: str = i64tos(ip, base.DEC); let k: i32 = 0; - for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; - os.free(intstr.ptr: *void, intstr.len: u64); + for (k < intstr.len) { f64tos_buf[out] = intstr.ptr[k]; out += 1; k += 1; }; if (fp != 0) { - tmp[out] = 46u8; // '.' + f64tos_buf[out] = 46u8; // '.' out += 1; - let fracstr: str = u64tos(fp: u64, DEC); + let fracstr: str = u64tos(fp: u64, base.DEC); // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → // fp=50000, fracstr="50000", pad one '0' before). let z: i32 = 6 - fracstr.len; - for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + for (z > 0) { f64tos_buf[out] = 48u8; out += 1; z -= 1; }; k = 0; - for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; - os.free(fracstr.ptr: *void, fracstr.len: u64); + for (k < fracstr.len) { f64tos_buf[out] = fracstr.ptr[k]; out += 1; k += 1; }; // Trim trailing zeros in the fractional part. for (out > 0) { - if (tmp[out - 1] != 48u8) { break; }; + if (f64tos_buf[out - 1] != 48u8) { break; }; out -= 1; }; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 500a2b3d..96eb8bf6 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -39,53 +39,46 @@ export fn hassuffix(s: str, suf: str) bool = { return true; }; -// indexbyte — first byte position of byte `c` in `s`. Mirrors -// Hare's strings::byteindex when the needle is a single ASCII rune, -// renamed to match bytes.indexbyte and to disambiguate from Hare's -// `byteindex(haystack, needle: (str | rune))` which we don't have -// the union-arg ABI for yet. -export fn indexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = 0; - for (i < s.len) { - if (s[i] == c) { return i; }; - i += 1; - }; - return; -}; - -// rindexbyte — last byte position of byte `c` in `s`. -export fn rindexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = s.len - 1; - for (i >= 0) { - if (s[i] == c) { return i; }; - i -= 1; - }; - return; -}; - -// index — first index of `sub` in `s`. Naive scan; fine for short -// patterns and small strings, which dominate config and CLI parsing. -// Empty `sub` matches at 0. -export fn index(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return; }; - let last: i32 = s.len - sub.len; - let i: i32 = 0; - for (i <= last) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// byteindex — first byte position of `needle` in `s`. Mirrors Hare's +// strings::byteindex: a single-codepoint rune scans for the byte that +// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), +// a str needle scans for the substring. Returns void if absent. +export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = 0; + for (i < s.len) { + if (s[i] == c) { return i; }; + i += 1; }; - if (ok) { return i; }; - i += 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return 0; }; + if (sub.len > s.len) { return; }; + let last: i32 = s.len - sub.len; + let i: i32 = 0; + for (i <= last) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i += 1; + }; + return; + }; }; return; }; +// contains — true iff `sub` appears in `s`. Mirrors Hare's +// strings::contains shape (byte-wise on the str-needle case). export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = index(s, sub); + let r: (i32 | void) = byteindex(s, sub); match (r) { case let i: i32 => return true; case void => return false; @@ -129,21 +122,37 @@ export fn dup(s: str) str = { return r; }; -// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex -// (slice case). Empty `sub` matches at s.len. -export fn rindex(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return s.len; }; - if (sub.len > s.len) { return; }; - let i: i32 = s.len - sub.len; - for (i >= 0) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's +// strings::rbyteindex. Rune needle scans for the byte that encodes it +// (ASCII only); str needle scans for the substring. Empty str needle +// matches at s.len. +export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = s.len - 1; + for (i >= 0) { + if (s[i] == c) { return i; }; + i -= 1; }; - if (ok) { return i; }; - i -= 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return s.len; }; + if (sub.len > s.len) { return; }; + let i: i32 = s.len - sub.len; + for (i >= 0) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i -= 1; + }; + return; + }; }; return; }; diff --git a/lib/ww/ast.ww b/lib/ww/ast.ww index eda454b5..11c2e48e 100644 --- a/lib/ww/ast.ww +++ b/lib/ww/ast.ww @@ -269,11 +269,11 @@ fn pr(fd: i32, n: *node, d: i32) void = { if (n.kind == nkind.N_INTLIT) { putc1(fd, 32u8); - let s: str = strconv.u64tos(n.uval, strconv.DEC); + let s: str = strconv.u64tos(n.uval, strconv.base.DEC); os.write(fd, s.ptr, s.len: u64); } else { if (n.kind == nkind.N_RUNELIT) { putc1(fd, 32u8); - let s: str = strconv.u64tos(n.uval, strconv.DEC); + let s: str = strconv.u64tos(n.uval, strconv.base.DEC); os.write(fd, s.ptr, s.len: u64); } else { if ( n.kind == nkind.N_STRLIT || diff --git a/lib/ww/lex/tok.ww b/lib/ww/lex/tok.ww index 9eb658d8..9f0a369f 100644 --- a/lib/ww/lex/tok.ww +++ b/lib/ww/lex/tok.ww @@ -382,10 +382,10 @@ export fn tokprint(fd: i32, t: *tok) void = { fputsstr(fd, ""); }; fputcbyte(fd, 58u8); // ':' - let ls: str = strconv.i64tos(t.line: i64, strconv.DEC); + let ls: str = strconv.i64tos(t.line: i64, strconv.base.DEC); os.write(fd, ls.ptr, ls.len: u64); fputcbyte(fd, 58u8); - let cs: str = strconv.i64tos(t.col: i64, strconv.DEC); + let cs: str = strconv.i64tos(t.col: i64, strconv.base.DEC); os.write(fd, cs.ptr, cs.len: u64); fputcbyte(fd, 32u8); // ' ' fputsstr(fd, tokname(t.kind)); @@ -401,11 +401,11 @@ export fn tokprint(fd: i32, t: *tok) void = { fputq(fd, ttext.ptr, ttext.len); } else { if (t.kind == tkind.TK_INT) { fputcbyte(fd, 32u8); - let us: str = strconv.u64tos(t.uval, strconv.DEC); + let us: str = strconv.u64tos(t.uval, strconv.base.DEC); os.write(fd, us.ptr, us.len: u64); } else { if (t.kind == tkind.TK_RUNE) { fputcbyte(fd, 32u8); - let us: str = strconv.u64tos(t.uval, strconv.DEC); + let us: str = strconv.u64tos(t.uval, strconv.base.DEC); os.write(fd, us.ptr, us.len: u64); };};};};}; // tkind.TK_FLOAT is intentionally not handled here — %g formatting diff --git a/selfhost/CLAUDE.md b/selfhost/CLAUDE.md index 144e1637..5979949a 100644 --- a/selfhost/CLAUDE.md +++ b/selfhost/CLAUDE.md @@ -27,5 +27,42 @@ Fixed (no workaround needed): Wwstage parser + cgen are byte-identical to C cgen on these shapes. See cmd/w6c/cgen.c N_RETURN/N_LET/N_DOT/N_MLET, lib/ww/parse/{stmt, expr}.ww and selfhost/cmd/wcc/{cgenstmt,cgenexpr,cgenutil,cgendecl}.ww. +- f64 compound assigns (`acc += d`, also `-= *= /=`) on locals and + top-level lets. Both stages now load slot into X1, OP X0 into X1 + (ADDSD/SUBSD/MULSD/DIVSD register-register), and store X1 back. + See cmd/w6c/cgen.c N_ASSIGN float-IDENT branch and + selfhost/cmd/wcc/cgenexpr.ww cgassign float local/global. +- Top-level `[N]T` arrays. The cstage cgen now emits a zero-init + DATAW slot and accesses go through `LEAQ name(SB)`; previously + the array was filtered out by `let_emit_size` and `arr[i]` fell + through to `LEAQ (BP), BX` (off-by-frame). `let_isarray` mirrors + the selfhost N_TARRAY path in `letemitsize`. +- `&arr[i]` (address-of an index). Both stages now compute base + + i*esz without a trailing dereference; the previous TK_AMP path + pre-evaluated the operand as if it were a value-load. Unblocks + Hare's `let s = string { data = &buf, ... }` static-buffer + shape. See cmd/w6c/cgen.c N_UN TK_AMP and + selfhost/cmd/wcc/cgenexpr.ww cgun TK_AMP. +- Tagged-union return ABI is now AX=tag, DX=word0, CX=word1, + R8=word2 (was AX/DX/CX, 3 words). Slice-payload variants + (`(slice | E)`, slot 32B) round-trip end-to-end. Every receive + site (let-init, match scrutinee spill, cgwidentaggedstore for + call-source, cgindex tagged-element load, pushargsrev tagged- + ident arg) reads the fourth word when slot size > 24. See + cmd/w6c/cgen.c N_RETURN / cg_widen_tagged_store and the + matching selfhost cgenstmt / cgenutil / cgenexpr branches. +- `expr: TaggedAlias` is a widening, not a re-interpret. + cgwidentaggedstore peels an `N_CAST` whose destination IS the + union itself, so cgexpr's natural register shape (str: AX=ptr, + BX=len) is consumed by the str-payload branch instead of being + misread as a tagged AX/DX/CX triple. Inner casts to a concrete + variant (`7: i32` in `(i64 | i32)`) keep their type so the + scalar branch picks the right variant tag. +- `[N]Alias` arrays read element size through `slotsize` so an + aliased tagged variant (e.g. `[3]fmt.formattable`) takes its + full 24B stride per element, not the 8B fallback. + selfhost/cmd/wcc/cgenutil.ww slotsize N_TARRAY follows + `aliaslookup` on a TNAME element, and `aliaslookup` strips a + `pkg.` prefix so cross-module references resolve. If a port "should work" but the binary is wrong, suspect these first. diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0fc60a28..51727a4c 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -377,53 +377,46 @@ export fn hassuffix(s: str, suf: str) bool = { return true; }; -// indexbyte — first byte position of byte `c` in `s`. Mirrors -// Hare's strings::byteindex when the needle is a single ASCII rune, -// renamed to match bytes.indexbyte and to disambiguate from Hare's -// `byteindex(haystack, needle: (str | rune))` which we don't have -// the union-arg ABI for yet. -export fn indexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = 0; - for (i < s.len) { - if (s[i] == c) { return i; }; - i += 1; - }; - return; -}; - -// rindexbyte — last byte position of byte `c` in `s`. -export fn rindexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = s.len - 1; - for (i >= 0) { - if (s[i] == c) { return i; }; - i -= 1; - }; - return; -}; - -// index — first index of `sub` in `s`. Naive scan; fine for short -// patterns and small strings, which dominate config and CLI parsing. -// Empty `sub` matches at 0. -export fn index(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return; }; - let last: i32 = s.len - sub.len; - let i: i32 = 0; - for (i <= last) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// byteindex — first byte position of `needle` in `s`. Mirrors Hare's +// strings::byteindex: a single-codepoint rune scans for the byte that +// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), +// a str needle scans for the substring. Returns void if absent. +export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = 0; + for (i < s.len) { + if (s[i] == c) { return i; }; + i += 1; }; - if (ok) { return i; }; - i += 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return 0; }; + if (sub.len > s.len) { return; }; + let last: i32 = s.len - sub.len; + let i: i32 = 0; + for (i <= last) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i += 1; + }; + return; + }; }; return; }; +// contains — true iff `sub` appears in `s`. Mirrors Hare's +// strings::contains shape (byte-wise on the str-needle case). export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = index(s, sub); + let r: (i32 | void) = byteindex(s, sub); match (r) { case let i: i32 => return true; case void => return false; @@ -467,21 +460,37 @@ export fn dup(s: str) str = { return r; }; -// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex -// (slice case). Empty `sub` matches at s.len. -export fn rindex(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return s.len; }; - if (sub.len > s.len) { return; }; - let i: i32 = s.len - sub.len; - for (i >= 0) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's +// strings::rbyteindex. Rune needle scans for the byte that encodes it +// (ASCII only); str needle scans for the substring. Empty str needle +// matches at s.len. +export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = s.len - 1; + for (i >= 0) { + if (s[i] == c) { return i; }; + i -= 1; }; - if (ok) { return i; }; - i -= 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return s.len; }; + if (sub.len > s.len) { return; }; + let i: i32 = s.len - sub.len; + for (i >= 0) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i -= 1; + }; + return; + }; }; return; }; @@ -558,12 +567,11 @@ export fn trimbyte(s: str, c: u8) str = { // MODULE: strconv // strconv — number↔string conversions. // -// Mirrors Hare's strconv:: surface. The *tos functions return a fresh -// owned `str`; release via os.free(r.ptr, r.len: u64) when done. -// Hare returns `const str` into a static buffer; ww allocates per -// call because the wwstage cgen doesn't currently support mutating a -// module-level `*u8` (so a lazy-init shared buffer isn't expressible -// today). Graduate to the static-buffer shape once that lands. +// Mirrors Hare's strconv:: surface. The *tos functions return a +// `const str` view into a module-level buffer that is overwritten on +// the next call to the same function; callers must copy the bytes if +// they need to outlive the next invocation. See [[strings.dup]] to +// duplicate. Matches Hare's strconv::*tos semantics. use os; use strings; @@ -580,43 +588,44 @@ export type overflow = !void; // error — any error from a strconv call. Mirrors Hare's strconv::error. export type error = !(invalid | overflow); -// base — numeric base for parsing/formatting. Plain i32 (not a named -// enum) because cross-module `strconv.base.DEC` chains miscompile in -// the cstage cgen — it emits a memory load through `base(SB)` rather -// than inlining the enum value. Hare names them as `strconv::base` -// enum values; we expose them as module-level `def`s so callers say -// `strconv.DEC` and the cgen inlines the immediate. +// base — numeric base for parsing/formatting. Mirrors Hare's +// `strconv::base` (Hare uses `enum uint`; we pick `enum i32` since +// the underlying parse/format loops index with i32). // -// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces -// lowercase a-f digits. -export def DEFAULT: i32 = 0; -export def BIN: i32 = 2; -export def OCT: i32 = 8; -export def DEC: i32 = 10; -export def HEX_UPPER: i32 = 16; -export def HEX: i32 = 16; -export def HEX_LOWER: i32 = 17; +// HEX is an alias for HEX_UPPER; HEX_LOWER is a pseudo-base that +// produces lowercase a-f digits. +export type base = enum i32 { + DEFAULT = 0, + BIN = 2, + OCT = 8, + DEC = 10, + HEX_UPPER = 16, + HEX = 16, + HEX_LOWER = 17, +}; -fn basenum(b: i32) i64 = { - if (b == BIN) { return 2; }; - if (b == OCT) { return 8; }; - if (b == HEX) { return 16; }; - if (b == HEX_UPPER) { return 16; }; - if (b == HEX_LOWER) { return 16; }; +fn basenum(b: base) i64 = { + if (b == base.BIN) { return 2; }; + if (b == base.OCT) { return 8; }; + if (b == base.HEX) { return 16; }; + if (b == base.HEX_UPPER) { return 16; }; + if (b == base.HEX_LOWER) { return 16; }; return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: i32) u8 = { +fn basedigit(d: i64, b: base) u8 = { if (d < 10) { return (d + 48): u8; }; let off: i64 = d - 10; - if (b == HEX_LOWER) { return (off + 97): u8; }; + if (b == base.HEX_LOWER) { return (off + 97): u8; }; return (off + 65): u8; }; -// u64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free(r.ptr, r.len: u64). Mirrors Hare's -// strconv::u64tos (Hare returns const str into a static buffer). -export fn u64tos(v: u64, b: i32) str = { +// u64tos — convert v to a base-b numeric string. Returns a view into +// `u64tos_buf` which is overwritten on the next call. Matches Hare's +// strconv::u64tos. +let u64tos_buf: [65]u8; + +export fn u64tos(v: u64, b: base) str = { let nb: u64 = basenum(b): u64; let tmp: [65]u8; let i: i32 = 0; @@ -628,22 +637,25 @@ export fn u64tos(v: u64, b: i32) str = { n = n / nb; i += 1; }; - let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; - buf[out] = tmp[i]; + u64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &u64tos_buf[0]; r.len = out; return r; }; -// i64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free. Mirrors Hare's strconv::i64tos. -export fn i64tos(v: i64, b: i32) str = { +// i64tos — convert v to a base-b numeric string. Returns a view into +// `i64tos_buf` which is overwritten on the next call. Independent +// buffer from u64tos so i64tos's own call to u64tos doesn't clobber +// the in-flight result. Matches Hare's strconv::i64tos. +let i64tos_buf: [66]u8; + +export fn i64tos(v: i64, b: base) str = { let neg: bool = false; let n: i64 = v; if (n < 0) { neg = true; n = -n; }; @@ -657,37 +669,33 @@ export fn i64tos(v: i64, b: i32) str = { n = n / nb; i += 1; }; - let extra: i32 = 0; - if (neg) { extra = 1; }; - let total: i32 = i + extra; - let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { buf[out] = 45u8; out += 1; }; // '-' + if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; - buf[out] = tmp[i]; + i64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &i64tos_buf[0]; r.len = out; return r; }; -export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; -export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; -export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; +export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; -export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; -export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; -export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; +export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; // digval — value of digit byte `c` under base `b`, or -1 if not a // valid digit. Letters are accepted case-insensitively under HEX / // HEX_UPPER; only lowercase under HEX_LOWER. -fn digval(c: u8, b: i32) i32 = { +fn digval(c: u8, b: base) i32 = { if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; - if (b == HEX_LOWER) { + if (b == base.HEX_LOWER) { if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; return -1; }; @@ -700,7 +708,7 @@ fn digval(c: u8, b: i32) i32 = { // No locale, no whitespace, no underscores: optional leading '-' then // digits. Returns invalid with the offending index or overflow on // out-of-range. -export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { +export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; @@ -721,7 +729,7 @@ export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { }; // stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. -export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { +export fn stou64(s: str, b: base) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let nb: u64 = basenum(b): u64; let v: u64 = 0u64; @@ -737,7 +745,7 @@ export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { return v; }; -export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { +export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -751,7 +759,7 @@ export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { return 0: invalid; // unreachable; appeases the path-cov checker }; -export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { +export fn stoi16(s: str, b: base) (i16 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -765,7 +773,7 @@ export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { return 0: invalid; }; -export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { +export fn stoi8(s: str, b: base) (i8 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -779,7 +787,7 @@ export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { return 0: invalid; }; -export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { +export fn stou32(s: str, b: base) (u32 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -792,7 +800,7 @@ export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { return 0: invalid; }; -export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { +export fn stou16(s: str, b: base) (u16 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -805,7 +813,7 @@ export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { return 0: invalid; }; -export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { +export fn stou8(s: str, b: base) (u8 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -841,13 +849,14 @@ export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. +let f64tos_buf: [64]u8; + export fn f64tos(v: f64) str = { - let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - tmp[out] = 45u8; // '-' + f64tos_buf[out] = 45u8; // '-' out += 1; f = -f; }; @@ -857,12 +866,9 @@ export fn f64tos(v: f64) str = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; + for (k < s.len) { f64tos_buf[out] = s[k]; out += 1; k += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -881,32 +887,27 @@ export fn f64tos(v: f64) str = { ip += 1; fp = 0; }; - let intstr: str = i64tos(ip, DEC); + let intstr: str = i64tos(ip, base.DEC); let k: i32 = 0; - for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; - os.free(intstr.ptr: *void, intstr.len: u64); + for (k < intstr.len) { f64tos_buf[out] = intstr.ptr[k]; out += 1; k += 1; }; if (fp != 0) { - tmp[out] = 46u8; // '.' + f64tos_buf[out] = 46u8; // '.' out += 1; - let fracstr: str = u64tos(fp: u64, DEC); + let fracstr: str = u64tos(fp: u64, base.DEC); // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → // fp=50000, fracstr="50000", pad one '0' before). let z: i32 = 6 - fracstr.len; - for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + for (z > 0) { f64tos_buf[out] = 48u8; out += 1; z -= 1; }; k = 0; - for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; - os.free(fracstr.ptr: *void, fracstr.len: u64); + for (k < fracstr.len) { f64tos_buf[out] = fracstr.ptr[k]; out += 1; k += 1; }; // Trim trailing zeros in the fractional part. for (out > 0) { - if (tmp[out - 1] != 48u8) { break; }; + if (f64tos_buf[out - 1] != 48u8) { break; }; out -= 1; }; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -1307,10 +1308,10 @@ export fn tokprint(fd: i32, t: *tok) void = { fputsstr(fd, ""); }; fputcbyte(fd, 58u8); // ':' - let ls: str = strconv.i64tos(t.line: i64, strconv.DEC); + let ls: str = strconv.i64tos(t.line: i64, strconv.base.DEC); os.write(fd, ls.ptr, ls.len: u64); fputcbyte(fd, 58u8); - let cs: str = strconv.i64tos(t.col: i64, strconv.DEC); + let cs: str = strconv.i64tos(t.col: i64, strconv.base.DEC); os.write(fd, cs.ptr, cs.len: u64); fputcbyte(fd, 32u8); // ' ' fputsstr(fd, tokname(t.kind)); @@ -1326,11 +1327,11 @@ export fn tokprint(fd: i32, t: *tok) void = { fputq(fd, ttext.ptr, ttext.len); } else { if (t.kind == tkind.TK_INT) { fputcbyte(fd, 32u8); - let us: str = strconv.u64tos(t.uval, strconv.DEC); + let us: str = strconv.u64tos(t.uval, strconv.base.DEC); os.write(fd, us.ptr, us.len: u64); } else { if (t.kind == tkind.TK_RUNE) { fputcbyte(fd, 32u8); - let us: str = strconv.u64tos(t.uval, strconv.DEC); + let us: str = strconv.u64tos(t.uval, strconv.base.DEC); os.write(fd, us.ptr, us.len: u64); };};};};}; // tkind.TK_FLOAT is intentionally not handled here — %g formatting @@ -2572,11 +2573,11 @@ fn pr(fd: i32, n: *node, d: i32) void = { if (n.kind == nkind.N_INTLIT) { putc1(fd, 32u8); - let s: str = strconv.u64tos(n.uval, strconv.DEC); + let s: str = strconv.u64tos(n.uval, strconv.base.DEC); os.write(fd, s.ptr, s.len: u64); } else { if (n.kind == nkind.N_RUNELIT) { putc1(fd, 32u8); - let s: str = strconv.u64tos(n.uval, strconv.DEC); + let s: str = strconv.u64tos(n.uval, strconv.base.DEC); os.write(fd, s.ptr, s.len: u64); } else { if ( n.kind == nkind.N_STRLIT || @@ -5648,7 +5649,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { return rest + widensz / 8; }; cgexpr(c, arg); - if (nodeisstr(c, arg)) { + if (nodeisslice(c, arg)) { + // Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, + // CX=cap). Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, + // [+24]=cap. Push high→low so pop drains tag first. + // Requires widensz >= 32; a smaller slot would mean the + // destination union doesn't list slice as a variant + // (caller should have flagged a type error). + emitline("\tPUSHQ\tCX\n"); + emitline("\tPUSHQ\tBX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tMOVQ\t$"); + emitint(widentag: i64); + emitline(", AX\n"); + emitline("\tPUSHQ\tAX\n"); + } else { 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"); @@ -5661,16 +5676,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { // 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) { + let pp: i32 = widensz - 8; + for (pp > 8) { emitline("\tXORQ\tDX, DX\n"); emitline("\tPUSHQ\tDX\n"); + pp -= 8; }; 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 @@ -5756,25 +5773,28 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { // Slice/tagged ident args: emit per-register MOVQ+PUSHQ pairs in // reverse order (cap/v1, len/v0, ptr/tag) so a left-to-right pop // into argregs lands the canonical (ptr/tag, len/v0, cap/v1). + // For tagged ident with a >24B slot (slice-payload variant), + // push a fourth word from off+24. if (arg.kind == nkind.N_IDENT) { let nm: str = arg.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let off: i32 = lc.off; if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 16): i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - return rest + 3; + let nwords: i32 = 3; + if (istaggedtype(c, lc.tnode)) { + let ssz: i32 = slotsize(c, lc.tnode); + nwords = ssz / 8; + }; + let w: i32 = nwords - 1; + for (w >= 0) { + emitline("\tMOVQ\t"); + emitoff((off + w*8): i64); + emitline("(BP), AX\n"); + emitline("\tPUSHQ\tAX\n"); + w -= 1; + }; + return rest + nwords; }; }; }; @@ -6157,6 +6177,9 @@ fn dotinnerstructptr(c: *cgen, n: *node) *node = { // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). +// For aliased element types (e.g. `[N]formattable`), callers that +// need the resolved slot size should use elemsizeofc(c, t) which +// follows aliases via slotsize. fn elemsizeof(t: *node) i32 = { if (t == nil) { return 1; }; let k: nkind = t.kind; @@ -6183,6 +6206,27 @@ fn elemsizeof(t: *node) i32 = { return 8; }; +// elemsizeofc — like elemsizeof but resolves aliased element types +// (struct / tagged / `type foo = bar;`) via slotsize. Used where +// cgindex / cgassign need a correct stride for `[N]Alias` arrays +// whose Alias resolves to a tagged union (e.g. `[N]formattable`). +fn elemsizeofc(c: *cgen, t: *node) i32 = { + if (t == nil) { return 1; }; + let direct: i32 = elemsizeof(t); + if (direct != 8) { return direct; }; + let k: nkind = t.kind; + let elem: *node = nil; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return direct; }; + if (elem.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elem.str); + if (ps > 0) { return ps; }; + }; + return slotsize(c, elem); +}; + // nodeisunsigned — best-effort cgen-time inference from the AST. We // don't have a typed AST yet, so we walk surface nodes: // nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet) @@ -6533,9 +6577,20 @@ fn slotsize(c: *cgen, typn: *node) i32 = { if (ps > 0) { esz = ps; } else { // Named struct / aliased type: size off - // the structinfo if present. + // the structinfo if present, else follow + // the alias via aliaslookup so + // `[N]formattable` reads the resolved + // tagged slot (e.g. 24B for + // `(i64|str|bool)`), not the fall- + // through 8B. let si: *structinfo = structlookup(c, en); - if (si != nil) { esz = si.totsize; }; + if (si != nil) { esz = si.totsize; } + else { if (c != nil) { + let al: *node = aliaslookup(c, en); + if (al != nil) { + esz = slotsize(c, al); + }; + }; }; }; } else { if (elemn.kind == nkind.N_TTAGGED) { // Tagged-union element: full slot (8 tag + @@ -7281,6 +7336,53 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitline("(BP)\n"); return; }; + // `expr: TaggedAlias` where the cast's destination IS the union + // itself is a widening, not a re-interpret. cgexpr on a CAST + // produces the inner's register shape (str: AX=ptr, BX=len), not + // the tagged AX/DX/CX triple — so peel to the inner and route + // through the matching concrete-variant branch below. A cast to + // a concrete variant (`7: i32`) is left intact so the existing + // scalar / str / slice branches pick the right variant tag. + if (src != nil) { + if (src.kind == nkind.N_CAST) { + if (src.lhs != nil) { + let inner: *node = src.lhs; + let inneristagged: bool = false; + if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + inneristagged = istaggedtype(c, lc.tnode); + }; + }; + if (rhstaggedabicall(c, inner)) { + inneristagged = true; + }; + // Cast's destination = the dst tagged union + // itself? The rhs of N_CAST holds the target + // type. Compare nominally via str match on + // the tagged-alias name. + let castisdst: bool = false; + let castrhs: *node = src.rhs; + if (castrhs != nil) { + if (castrhs.kind == nkind.N_TTAGGED) { + castisdst = true; + }; + if (castrhs.kind == nkind.N_TNAME) { + if (dst != nil) { + if (dst.kind == nkind.N_TNAME) { + if (streq(castrhs.str, dst.str)) { + castisdst = true; + }; + }; + }; + }; + }; + if (castisdst && !inneristagged) { + src = inner; + }; + }; + }; + }; // Tagged source ident: byte-copy slot words then tag-remap. let st: *node = rhstaggedident(c, src); if (st != nil) { @@ -7310,8 +7412,9 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: cgwidentagremap(c, dt, st, slot_off); return; }; - // Tagged source via AX/DX/CX register ABI (N_CALL, N_INDEX of - // tagged element). + // Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX + // of tagged element). R8 carries the 4th word for slice-payload + // variants (slot 32B). if (rhstaggedabicall(c, src)) { cgexpr(c, src); emitline("\tMOVQ\tAX, "); @@ -7327,6 +7430,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitoff((slot_off + 16): i64); emitline("(BP)\n"); }; + if (slot_sz > 24) { + emitline("\tMOVQ\tR8, "); + emitoff((slot_off + 24): i64); + emitline("(BP)\n"); + }; return; }; // Struct payload (literal or ident). @@ -7445,6 +7553,28 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitline("(BP)\n"); return; }; + // Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, CX=cap). + // Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, [+24]=cap. + if (nodeisslice(c, src)) { + cgexpr(c, src); + emitline("\tMOVQ\tAX, "); + emitoff((slot_off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((slot_off + 16): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((slot_off + 24): i64); + emitline("(BP)\n"); + let tag: i32 = taggedvariantindex(c, dt, src); + if (tag < 0) { tag = 0; }; + emitline("\tMOVQ\t$"); + emitint(tag: i64); + emitline(", "); + emitoff(slot_off: i64); + emitline("(BP)\n"); + return; + }; // Scalar payload. cgexpr(c, src); emitline("\tMOVQ\tAX, "); @@ -7997,7 +8127,7 @@ fn cgindex(c: *cgen, n: *node) void = { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); + esz = elemsizeofc(c, baselocal.tnode); signed_elem = elemissigned(baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); @@ -8005,13 +8135,13 @@ fn cgindex(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; }; @@ -8076,6 +8206,9 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { + if (elem_slot_sz > 24) { + emitline("\tMOVQ\t24(BX), R8\n"); + }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; @@ -8114,6 +8247,9 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { + if (elem_slot_sz > 24) { + emitline("\tMOVQ\t24(BX), R8\n"); + }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; @@ -8272,7 +8408,7 @@ fn cgmatch(c: *cgen, n: *node) void = { }; } else { // Non-ident scrutinee (call result, arr[i], ?, etc.). - // Spill into a 24B `@match_spill` scratch slot and + // Spill into an `@match_spill` scratch slot and // dispatch off it. Tagged returns (N_CALL) follow the // AX:DX:CX convention; tagged-element loads (N_INDEX) // after the cgindex fix produce the same triple. @@ -8327,6 +8463,16 @@ fn cgmatch(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, "); emitoff((scrutoff + 16): i64); emitline("(BP)\n"); + // R8 carries the 4th return word when the + // scrutinee's tagged union has a slice-payload + // variant (slot 32B). Harmless for narrower + // returns — R8 is callee-clobbered either way. + let ssz: i32 = slotsize(c, scrutt); + if (ssz > 24) { + emitline("\tMOVQ\tR8, "); + emitoff((scrutoff + 24): i64); + emitline("(BP)\n"); + }; }; }; }; @@ -8412,25 +8558,22 @@ fn cgmatch(c: *cgen, n: *node) void = { }; } else { let bsz: i32 = 8; - if (isstrtype(c, pat)) { bsz = 16; }; + if (isstrtype(c, pat)) { bsz = 16; } + else { if (isslicetype(c, pat)) { bsz = 24; }; }; // localalloc (not localadd): match-arm // binds don't dedup with same-named binds // in *other* matches, since C's cgexpr // allocates a fresh slot per match expr. let voff: i32 = localalloc(c, bn, bsz, pat); - emitline("\tMOVQ\t"); - emitoff((scrutoff + 8): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(voff: i64); - emitline("(BP)\n"); - if (bsz == 16) { + let bw: i32 = 0; + for (bw < bsz) { emitline("\tMOVQ\t"); - emitoff((scrutoff + 16): i64); + emitoff((scrutoff + 8 + bw): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); - emitoff((voff + 8): i64); + emitoff((voff + bw): i64); emitline("(BP)\n"); + bw += 8; }; }; }; @@ -9052,6 +9195,20 @@ fn cgdot(c: *cgen, n: *node) void = { };}; }; }; + // Nested module-qualified field where the chain didn't fold to a + // known shape (raw w6c on a single file with `use mod;` but no + // driver concatenation — the inner enum / struct hasn't been + // seen). Emit `MOVQ (SB), AX` so the linker surfaces a + // clean undefined-symbol error on the leaf. Mirror of + // cmd/w6c/cgen.c N_DOT nested fallback. + if (lhs != nil) { + if (lhs.kind == nkind.N_DOT) { + emitline("\tMOVQ\t"); + emitsymname(c, fld); + emitline("(SB), AX\n"); + return; + }; + }; return; }; @@ -9080,10 +9237,9 @@ fn cgun(c: *cgen, n: *node) void = { emitline("\t"); emitline(sub); emitline("\tX1, X0\n"); return; }; - cgexpr(c, n.lhs); - if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; - if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; - if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; + // Address-of has its own evaluation strategy — we want the address + // of the operand, not its value. Special-case here so `&arr[i]` + // doesn't compile the value load and then discard it. if (n.op == tkind.TK_AMP) { let opnd: *node = n.lhs; if (opnd != nil) { @@ -9096,17 +9252,94 @@ fn cgun(c: *cgen, n: *node) void = { emitline("(BP), AX\n"); return; }; - // Top-level mutable let — RIP-relative LEAQ. if (isletvar(c, nm)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; + return; + }; + if (opnd.kind == nkind.N_INDEX) { + // &base[i] = base + i*esz, no dereference. + let base: *node = opnd.lhs; + let idx: *node = opnd.rhs; + let esz: i32 = 8; + let isglobalarr: bool = false; + let isglobalptr: bool = false; + let globalname: str; + globalname.ptr = nil; globalname.len = 0; + let baselocal: *local = nil; + let isarr: bool = false; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + baselocal = localfindnode(c, base.str); + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + let tn: *node = baselocal.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { isarr = true; }; + }; + } else { + let tn: *node = letvartnode(c, base.str); + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + isglobalarr = true; + globalname = base.str; + esz = elemsizeofc(c, tn); + }; + if (tn.kind == nkind.N_TPTR) { + isglobalptr = true; + globalname = base.str; + esz = elemsizeofc(c, tn); + }; + }; + }; + }; + }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { + if (isarr) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { + // Complex base: spill scaled idx, eval + // base to AX, move to BX, restore idx. + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + emitline("\tPOPQ\tAX\n"); + };};}; + emitline("\tADDQ\tBX, AX\n"); + return; }; }; return; }; + cgexpr(c, n.lhs); + if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; + if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; + if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; if (n.op == tkind.TK_NOT) { let t: str = mklabel(c, "tt"); let e: str = mklabel(c, "te"); @@ -9812,7 +10045,7 @@ fn cgassign(c: *cgen, n: *node) void = { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); + esz = elemsizeofc(c, baselocal.tnode); let btn: *node = baselocal.tnode; if (btn != nil) { let bk: nkind = btn.kind; @@ -9826,13 +10059,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; }; @@ -10526,16 +10759,55 @@ fn cgassign(c: *cgen, n: *node) void = { lvf = lvf.lvnext; }; }; - if (isfg && n.op == tkind.TK_ASSIGN) { + if (isfg) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; - if (isf32g) { mov = "MOVSS"; }; + let addf: str = "ADDSD"; + let subf: str = "SUBSD"; + let mulf: str = "MULSD"; + let divf: str = "DIVSD"; + if (isf32g) { + mov = "MOVSS"; + addf = "ADDSS"; + subf = "SUBSS"; + mulf = "MULSS"; + divf = "DIVSS"; + }; emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); + if (n.op == tkind.TK_ASSIGN) { + emitline("\t"); + emitline(mov); + emitline("\tX0, (CX)\n"); + return; + }; + // Compound: X1 = load; X1 OP= X0; store X1. + // ADDSD/SUBSD/MULSD/DIVSD are register-register + // only, so we can't combine direct to memory. + let fop: str; + fop.ptr = nil; fop.len = 0; + if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; + if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; + if (n.op == tkind.TK_STAREQ) { fop = mulf; }; + if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; + if (fop.len == 0) { + // Unsupported (e.g., %= on float): + // fall back to plain store of rhs. + emitline("\t"); + emitline(mov); + emitline("\tX0, (CX)\n"); + return; + }; emitline("\t"); emitline(mov); - emitline("\tX0, (CX)\n"); + emitline("\t(CX), X1\n"); + emitline("\t"); + emitline(fop); + emitline("\tX0, X1\n"); + emitline("\t"); + emitline(mov); + emitline("\tX1, (CX)\n"); return; }; cgexpr(c, n.rhs); @@ -10614,15 +10886,56 @@ fn cgassign(c: *cgen, n: *node) void = { lcf32 = isf32type(c, lcn.tnode); }; // Float-typed local: rhs lands in X0; store via MOVSD/ - // MOVSS, no AX shuffle. Only plain `=` is wired; compound - // float-assign isn't. - if (lcf && n.op == tkind.TK_ASSIGN) { + // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads + // slot into X1, combines into X1, stores X1 back — + // ADDSD/SUBSD/MULSD/DIVSD are register-register only. + if (lcf) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; - if (lcf32) { mov = "MOVSS"; }; + let addf: str = "ADDSD"; + let subf: str = "SUBSD"; + let mulf: str = "MULSD"; + let divf: str = "DIVSD"; + if (lcf32) { + mov = "MOVSS"; + addf = "ADDSS"; + subf = "SUBSS"; + mulf = "MULSS"; + divf = "DIVSS"; + }; + if (n.op == tkind.TK_ASSIGN) { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + let fop: str; + fop.ptr = nil; fop.len = 0; + if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; + if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; + if (n.op == tkind.TK_STAREQ) { fop = mulf; }; + if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; + if (fop.len == 0) { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; emitline("\t"); emitline(mov); - emitline("\tX0, "); + emitline("\t"); + emitoff(off: i64); + emitline("(BP), X1\n"); + emitline("\t"); + emitline(fop); + emitline("\tX0, X1\n"); + emitline("\t"); + emitline(mov); + emitline("\tX1, "); emitoff(off: i64); emitline("(BP)\n"); return; @@ -10889,6 +11202,11 @@ fn cgreturn(c: *cgen, n: *node) void = { emitoff((scroff + 16): i64); emitline("(BP), CX\n"); }; + if (rsz > 24) { + emitline("\tMOVQ\t"); + emitoff((scroff + 24): i64); + emitline("(BP), R8\n"); + }; emitline("\tMOVQ\tBP, SP\n"); emitline("\tPOPQ\tBP\n"); emitline("\tRET\n"); @@ -10911,12 +11229,19 @@ fn cgreturn(c: *cgen, n: *node) void = { return; }; let idx: i32 = taggedvariantindex(c, c.fnret, rhs); - if (nodeisstr(c, rhs)) { + if (nodeisslice(c, rhs)) { + // cgexpr leaves (AX=ptr, BX=len, CX=cap). + // Shuffle into return ABI: DX=ptr, CX=len, + // R8=cap. + emitline("\tMOVQ\tCX, R8\n"); + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tMOVQ\tAX, DX\n"); + } else { if (nodeisstr(c, rhs)) { emitline("\tMOVQ\tBX, CX\n"); emitline("\tMOVQ\tAX, DX\n"); } else { emitline("\tMOVQ\tAX, DX\n"); - }; + };}; emitline("\tMOVQ\t$"); if (idx < 0) { idx = 0; }; emitint(idx: i64); @@ -11928,10 +12253,24 @@ fn scanlocals(c: *cgen, n: *node) i32 = { if (bn.len > 0) { let pat: *node = n.lhs; if (pat != nil) { - if (isstrtype(c, pat)) { total += 16; } - else { total += 8; }; + if (isstrtype(c, pat)) { total += 16; } + else { if (isslicetype(c, pat)) { total += 24; } + else { total += 8; }; }; }; }; + // Match arms get a fresh local scope at emission time + // (cgmatch saves c.locals before each arm and restores + // after). scanlocals must mirror that: walk the arm + // body with a saved/restored seenmark set so two arms + // declaring the same name each get their own slot, + // matching the per-arm frame growth the emit phase + // produces. + if (n.body != nil) { + let saved: *local = c.locals; + total += scanlocals(c, n.body); + c.locals = saved; + }; + return total; }; // Tagged-arr/slice index store needs a 24B scratch slot // (`@tagscr`) for cgwidentaggedstore to materialise the source @@ -12124,10 +12463,36 @@ fn cgfnparams(c: *cgen, params: *node) void = { idx += 1; w += 1; }; + } else { if (idx < 6 && nw > 1) { + // Partial fit: fill remaining regs, then read + // the tail from positive BP offsets. Mirrors + // the caller's greedy reg fill in pushargsrev. + let off: i32 = localadd(c, nm, slot, p.lhs); + let regs_left: i32 = 6 - idx; + let w: i32 = 0; + for (w < regs_left) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + for (w < nw) { + emitline("\tMOVQ\t"); + emitoff((16 + stkcursor*8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + stkcursor += 1; + w += 1; + }; } else { localaddstack(c, nm, p.lhs, 16 + stkcursor*8); stkcursor += nw; - }; + };}; } else { if (isslicetype(c, p.lhs)) { if (idx + 3 <= 6) { let off: i32 = localadd(c, nm, 24, p.lhs); @@ -12208,7 +12573,12 @@ fn cgfn(c: *cgen, fn_: *node) void = { }; a = a.next; }; - if (!isffi) { + // `main` is the linker entry-point convention; even + // when not marked `export`, it must keep its bare + // name so w6l's _start can resolve `CALL main(SB)`. + // Mirror of cmd/w6c/cgen.c collectmods exemption. + let isentry: bool = streq(fn_.str, "main"); + if (!isffi && !isentry) { os.write(1, fn_.module.ptr, fn_.module.len: u64); os.write(1, ".".ptr, 1u64); }; @@ -12380,6 +12750,26 @@ fn aliaslookup(c: *cgen, name: str) *node = { if (streq(an, name)) { return a.target; }; a = a.aanext; }; + // Module-qualified form: `pkg.alias` → try the bare leaf so a + // cross-module reference resolves the same way bare access does + // after driver concatenation. Mirrors the check.c module- + // qualified type resolution. + let i: i32 = name.len - 1; + for (i >= 0) { + if (name[i] == 46u8) { // '.' + let leaf: str; + leaf.ptr = name.ptr + ((i + 1): u64); + leaf.len = name.len - (i + 1); + let b: *aliasent = c.aliases; + for (b != nil) { + if (streq(b.aname, leaf)) { return b.target; }; + b = b.aanext; + }; + i = -1; + } else { + i -= 1; + }; + }; return nil; }; @@ -12775,12 +13165,12 @@ fn localfind(c: *cgen, name: str) i32 = { fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); }; fn emitint(v: i64) void = { - let s: str = strconv.i64tos(v, strconv.DEC); + let s: str = strconv.i64tos(v, strconv.base.DEC); os.write(1, s.ptr, s.len: u64); }; fn emituint(v: u64) void = { - let s: str = strconv.u64tos(v, strconv.DEC); + let s: str = strconv.u64tos(v, strconv.base.DEC); os.write(1, s.ptr, s.len: u64); }; @@ -12818,7 +13208,7 @@ fn mklabel(c: *cgen, prefix: str) str = { i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; @@ -12857,7 +13247,7 @@ fn mkscratchname(c: *cgen, prefix: str) str = { i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; @@ -12894,7 +13284,7 @@ fn internstrlit(c: *cgen, bytes: str) str = { // New label "_S_". let buf: [32]u8; buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" - let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index bc05c1d9..ad36afc2 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -77,6 +77,26 @@ fn aliaslookup(c: *cgen, name: str) *node = { if (streq(an, name)) { return a.target; }; a = a.aanext; }; + // Module-qualified form: `pkg.alias` → try the bare leaf so a + // cross-module reference resolves the same way bare access does + // after driver concatenation. Mirrors the check.c module- + // qualified type resolution. + let i: i32 = name.len - 1; + for (i >= 0) { + if (name[i] == 46u8) { // '.' + let leaf: str; + leaf.ptr = name.ptr + ((i + 1): u64); + leaf.len = name.len - (i + 1); + let b: *aliasent = c.aliases; + for (b != nil) { + if (streq(b.aname, leaf)) { return b.target; }; + b = b.aanext; + }; + i = -1; + } else { + i -= 1; + }; + }; return nil; }; @@ -472,12 +492,12 @@ fn localfind(c: *cgen, name: str) i32 = { fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); }; fn emitint(v: i64) void = { - let s: str = strconv.i64tos(v, strconv.DEC); + let s: str = strconv.i64tos(v, strconv.base.DEC); os.write(1, s.ptr, s.len: u64); }; fn emituint(v: u64) void = { - let s: str = strconv.u64tos(v, strconv.DEC); + let s: str = strconv.u64tos(v, strconv.base.DEC); os.write(1, s.ptr, s.len: u64); }; @@ -515,7 +535,7 @@ fn mklabel(c: *cgen, prefix: str) str = { i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; @@ -554,7 +574,7 @@ fn mkscratchname(c: *cgen, prefix: str) str = { i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; @@ -591,7 +611,7 @@ fn internstrlit(c: *cgen, bytes: str) str = { // New label "_S_". let buf: [32]u8; buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" - let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; }; diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index aad9c606..7c4690bd 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -150,10 +150,24 @@ fn scanlocals(c: *cgen, n: *node) i32 = { if (bn.len > 0) { let pat: *node = n.lhs; if (pat != nil) { - if (isstrtype(c, pat)) { total += 16; } - else { total += 8; }; + if (isstrtype(c, pat)) { total += 16; } + else { if (isslicetype(c, pat)) { total += 24; } + else { total += 8; }; }; }; }; + // Match arms get a fresh local scope at emission time + // (cgmatch saves c.locals before each arm and restores + // after). scanlocals must mirror that: walk the arm + // body with a saved/restored seenmark set so two arms + // declaring the same name each get their own slot, + // matching the per-arm frame growth the emit phase + // produces. + if (n.body != nil) { + let saved: *local = c.locals; + total += scanlocals(c, n.body); + c.locals = saved; + }; + return total; }; // Tagged-arr/slice index store needs a 24B scratch slot // (`@tagscr`) for cgwidentaggedstore to materialise the source @@ -346,10 +360,36 @@ fn cgfnparams(c: *cgen, params: *node) void = { idx += 1; w += 1; }; + } else { if (idx < 6 && nw > 1) { + // Partial fit: fill remaining regs, then read + // the tail from positive BP offsets. Mirrors + // the caller's greedy reg fill in pushargsrev. + let off: i32 = localadd(c, nm, slot, p.lhs); + let regs_left: i32 = 6 - idx; + let w: i32 = 0; + for (w < regs_left) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + for (w < nw) { + emitline("\tMOVQ\t"); + emitoff((16 + stkcursor*8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + stkcursor += 1; + w += 1; + }; } else { localaddstack(c, nm, p.lhs, 16 + stkcursor*8); stkcursor += nw; - }; + };}; } else { if (isslicetype(c, p.lhs)) { if (idx + 3 <= 6) { let off: i32 = localadd(c, nm, 24, p.lhs); @@ -430,7 +470,12 @@ fn cgfn(c: *cgen, fn_: *node) void = { }; a = a.next; }; - if (!isffi) { + // `main` is the linker entry-point convention; even + // when not marked `export`, it must keep its bare + // name so w6l's _start can resolve `CALL main(SB)`. + // Mirror of cmd/w6c/cgen.c collectmods exemption. + let isentry: bool = streq(fn_.str, "main"); + if (!isffi && !isentry) { os.write(1, fn_.module.ptr, fn_.module.len: u64); os.write(1, ".".ptr, 1u64); }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 80409b29..85300316 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -534,7 +534,7 @@ fn cgindex(c: *cgen, n: *node) void = { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); + esz = elemsizeofc(c, baselocal.tnode); signed_elem = elemissigned(baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); @@ -542,13 +542,13 @@ fn cgindex(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; }; @@ -613,6 +613,9 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { + if (elem_slot_sz > 24) { + emitline("\tMOVQ\t24(BX), R8\n"); + }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; @@ -651,6 +654,9 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { + if (elem_slot_sz > 24) { + emitline("\tMOVQ\t24(BX), R8\n"); + }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; @@ -809,7 +815,7 @@ fn cgmatch(c: *cgen, n: *node) void = { }; } else { // Non-ident scrutinee (call result, arr[i], ?, etc.). - // Spill into a 24B `@match_spill` scratch slot and + // Spill into an `@match_spill` scratch slot and // dispatch off it. Tagged returns (N_CALL) follow the // AX:DX:CX convention; tagged-element loads (N_INDEX) // after the cgindex fix produce the same triple. @@ -864,6 +870,16 @@ fn cgmatch(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, "); emitoff((scrutoff + 16): i64); emitline("(BP)\n"); + // R8 carries the 4th return word when the + // scrutinee's tagged union has a slice-payload + // variant (slot 32B). Harmless for narrower + // returns — R8 is callee-clobbered either way. + let ssz: i32 = slotsize(c, scrutt); + if (ssz > 24) { + emitline("\tMOVQ\tR8, "); + emitoff((scrutoff + 24): i64); + emitline("(BP)\n"); + }; }; }; }; @@ -949,25 +965,22 @@ fn cgmatch(c: *cgen, n: *node) void = { }; } else { let bsz: i32 = 8; - if (isstrtype(c, pat)) { bsz = 16; }; + if (isstrtype(c, pat)) { bsz = 16; } + else { if (isslicetype(c, pat)) { bsz = 24; }; }; // localalloc (not localadd): match-arm // binds don't dedup with same-named binds // in *other* matches, since C's cgexpr // allocates a fresh slot per match expr. let voff: i32 = localalloc(c, bn, bsz, pat); - emitline("\tMOVQ\t"); - emitoff((scrutoff + 8): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(voff: i64); - emitline("(BP)\n"); - if (bsz == 16) { + let bw: i32 = 0; + for (bw < bsz) { emitline("\tMOVQ\t"); - emitoff((scrutoff + 16): i64); + emitoff((scrutoff + 8 + bw): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); - emitoff((voff + 8): i64); + emitoff((voff + bw): i64); emitline("(BP)\n"); + bw += 8; }; }; }; @@ -1589,6 +1602,20 @@ fn cgdot(c: *cgen, n: *node) void = { };}; }; }; + // Nested module-qualified field where the chain didn't fold to a + // known shape (raw w6c on a single file with `use mod;` but no + // driver concatenation — the inner enum / struct hasn't been + // seen). Emit `MOVQ (SB), AX` so the linker surfaces a + // clean undefined-symbol error on the leaf. Mirror of + // cmd/w6c/cgen.c N_DOT nested fallback. + if (lhs != nil) { + if (lhs.kind == nkind.N_DOT) { + emitline("\tMOVQ\t"); + emitsymname(c, fld); + emitline("(SB), AX\n"); + return; + }; + }; return; }; @@ -1617,10 +1644,9 @@ fn cgun(c: *cgen, n: *node) void = { emitline("\t"); emitline(sub); emitline("\tX1, X0\n"); return; }; - cgexpr(c, n.lhs); - if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; - if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; - if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; + // Address-of has its own evaluation strategy — we want the address + // of the operand, not its value. Special-case here so `&arr[i]` + // doesn't compile the value load and then discard it. if (n.op == tkind.TK_AMP) { let opnd: *node = n.lhs; if (opnd != nil) { @@ -1633,17 +1659,94 @@ fn cgun(c: *cgen, n: *node) void = { emitline("(BP), AX\n"); return; }; - // Top-level mutable let — RIP-relative LEAQ. if (isletvar(c, nm)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; + return; + }; + if (opnd.kind == nkind.N_INDEX) { + // &base[i] = base + i*esz, no dereference. + let base: *node = opnd.lhs; + let idx: *node = opnd.rhs; + let esz: i32 = 8; + let isglobalarr: bool = false; + let isglobalptr: bool = false; + let globalname: str; + globalname.ptr = nil; globalname.len = 0; + let baselocal: *local = nil; + let isarr: bool = false; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + baselocal = localfindnode(c, base.str); + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + let tn: *node = baselocal.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { isarr = true; }; + }; + } else { + let tn: *node = letvartnode(c, base.str); + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + isglobalarr = true; + globalname = base.str; + esz = elemsizeofc(c, tn); + }; + if (tn.kind == nkind.N_TPTR) { + isglobalptr = true; + globalname = base.str; + esz = elemsizeofc(c, tn); + }; + }; + }; + }; + }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { + if (isarr) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { + // Complex base: spill scaled idx, eval + // base to AX, move to BX, restore idx. + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + emitline("\tPOPQ\tAX\n"); + };};}; + emitline("\tADDQ\tBX, AX\n"); + return; }; }; return; }; + cgexpr(c, n.lhs); + if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; + if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; + if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; if (n.op == tkind.TK_NOT) { let t: str = mklabel(c, "tt"); let e: str = mklabel(c, "te"); @@ -2349,7 +2452,7 @@ fn cgassign(c: *cgen, n: *node) void = { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); + esz = elemsizeofc(c, baselocal.tnode); let btn: *node = baselocal.tnode; if (btn != nil) { let bk: nkind = btn.kind; @@ -2363,13 +2466,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; }; @@ -3063,16 +3166,55 @@ fn cgassign(c: *cgen, n: *node) void = { lvf = lvf.lvnext; }; }; - if (isfg && n.op == tkind.TK_ASSIGN) { + if (isfg) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; - if (isf32g) { mov = "MOVSS"; }; + let addf: str = "ADDSD"; + let subf: str = "SUBSD"; + let mulf: str = "MULSD"; + let divf: str = "DIVSD"; + if (isf32g) { + mov = "MOVSS"; + addf = "ADDSS"; + subf = "SUBSS"; + mulf = "MULSS"; + divf = "DIVSS"; + }; emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); + if (n.op == tkind.TK_ASSIGN) { + emitline("\t"); + emitline(mov); + emitline("\tX0, (CX)\n"); + return; + }; + // Compound: X1 = load; X1 OP= X0; store X1. + // ADDSD/SUBSD/MULSD/DIVSD are register-register + // only, so we can't combine direct to memory. + let fop: str; + fop.ptr = nil; fop.len = 0; + if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; + if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; + if (n.op == tkind.TK_STAREQ) { fop = mulf; }; + if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; + if (fop.len == 0) { + // Unsupported (e.g., %= on float): + // fall back to plain store of rhs. + emitline("\t"); + emitline(mov); + emitline("\tX0, (CX)\n"); + return; + }; emitline("\t"); emitline(mov); - emitline("\tX0, (CX)\n"); + emitline("\t(CX), X1\n"); + emitline("\t"); + emitline(fop); + emitline("\tX0, X1\n"); + emitline("\t"); + emitline(mov); + emitline("\tX1, (CX)\n"); return; }; cgexpr(c, n.rhs); @@ -3151,15 +3293,56 @@ fn cgassign(c: *cgen, n: *node) void = { lcf32 = isf32type(c, lcn.tnode); }; // Float-typed local: rhs lands in X0; store via MOVSD/ - // MOVSS, no AX shuffle. Only plain `=` is wired; compound - // float-assign isn't. - if (lcf && n.op == tkind.TK_ASSIGN) { + // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads + // slot into X1, combines into X1, stores X1 back — + // ADDSD/SUBSD/MULSD/DIVSD are register-register only. + if (lcf) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; - if (lcf32) { mov = "MOVSS"; }; + let addf: str = "ADDSD"; + let subf: str = "SUBSD"; + let mulf: str = "MULSD"; + let divf: str = "DIVSD"; + if (lcf32) { + mov = "MOVSS"; + addf = "ADDSS"; + subf = "SUBSS"; + mulf = "MULSS"; + divf = "DIVSS"; + }; + if (n.op == tkind.TK_ASSIGN) { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + let fop: str; + fop.ptr = nil; fop.len = 0; + if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; + if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; + if (n.op == tkind.TK_STAREQ) { fop = mulf; }; + if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; + if (fop.len == 0) { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; emitline("\t"); emitline(mov); - emitline("\tX0, "); + emitline("\t"); + emitoff(off: i64); + emitline("(BP), X1\n"); + emitline("\t"); + emitline(fop); + emitline("\tX0, X1\n"); + emitline("\t"); + emitline(mov); + emitline("\tX1, "); emitoff(off: i64); emitline("(BP)\n"); return; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 816c2b7d..84db4d91 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -203,6 +203,11 @@ fn cgreturn(c: *cgen, n: *node) void = { emitoff((scroff + 16): i64); emitline("(BP), CX\n"); }; + if (rsz > 24) { + emitline("\tMOVQ\t"); + emitoff((scroff + 24): i64); + emitline("(BP), R8\n"); + }; emitline("\tMOVQ\tBP, SP\n"); emitline("\tPOPQ\tBP\n"); emitline("\tRET\n"); @@ -225,12 +230,19 @@ fn cgreturn(c: *cgen, n: *node) void = { return; }; let idx: i32 = taggedvariantindex(c, c.fnret, rhs); - if (nodeisstr(c, rhs)) { + if (nodeisslice(c, rhs)) { + // cgexpr leaves (AX=ptr, BX=len, CX=cap). + // Shuffle into return ABI: DX=ptr, CX=len, + // R8=cap. + emitline("\tMOVQ\tCX, R8\n"); + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tMOVQ\tAX, DX\n"); + } else { if (nodeisstr(c, rhs)) { emitline("\tMOVQ\tBX, CX\n"); emitline("\tMOVQ\tAX, DX\n"); } else { emitline("\tMOVQ\tAX, DX\n"); - }; + };}; emitline("\tMOVQ\t$"); if (idx < 0) { idx = 0; }; emitint(idx: i64); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index a4066a6e..e73a7c23 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -101,7 +101,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { return rest + widensz / 8; }; cgexpr(c, arg); - if (nodeisstr(c, arg)) { + if (nodeisslice(c, arg)) { + // Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, + // CX=cap). Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, + // [+24]=cap. Push high→low so pop drains tag first. + // Requires widensz >= 32; a smaller slot would mean the + // destination union doesn't list slice as a variant + // (caller should have flagged a type error). + emitline("\tPUSHQ\tCX\n"); + emitline("\tPUSHQ\tBX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tMOVQ\t$"); + emitint(widentag: i64); + emitline(", AX\n"); + emitline("\tPUSHQ\tAX\n"); + } else { 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"); @@ -114,16 +128,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { // 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) { + let pp: i32 = widensz - 8; + for (pp > 8) { emitline("\tXORQ\tDX, DX\n"); emitline("\tPUSHQ\tDX\n"); + pp -= 8; }; 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 @@ -209,25 +225,28 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { // Slice/tagged ident args: emit per-register MOVQ+PUSHQ pairs in // reverse order (cap/v1, len/v0, ptr/tag) so a left-to-right pop // into argregs lands the canonical (ptr/tag, len/v0, cap/v1). + // For tagged ident with a >24B slot (slice-payload variant), + // push a fourth word from off+24. if (arg.kind == nkind.N_IDENT) { let nm: str = arg.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let off: i32 = lc.off; if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 16): i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - return rest + 3; + let nwords: i32 = 3; + if (istaggedtype(c, lc.tnode)) { + let ssz: i32 = slotsize(c, lc.tnode); + nwords = ssz / 8; + }; + let w: i32 = nwords - 1; + for (w >= 0) { + emitline("\tMOVQ\t"); + emitoff((off + w*8): i64); + emitline("(BP), AX\n"); + emitline("\tPUSHQ\tAX\n"); + w -= 1; + }; + return rest + nwords; }; }; }; @@ -610,6 +629,9 @@ fn dotinnerstructptr(c: *cgen, n: *node) *node = { // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). +// For aliased element types (e.g. `[N]formattable`), callers that +// need the resolved slot size should use elemsizeofc(c, t) which +// follows aliases via slotsize. fn elemsizeof(t: *node) i32 = { if (t == nil) { return 1; }; let k: nkind = t.kind; @@ -636,6 +658,27 @@ fn elemsizeof(t: *node) i32 = { return 8; }; +// elemsizeofc — like elemsizeof but resolves aliased element types +// (struct / tagged / `type foo = bar;`) via slotsize. Used where +// cgindex / cgassign need a correct stride for `[N]Alias` arrays +// whose Alias resolves to a tagged union (e.g. `[N]formattable`). +fn elemsizeofc(c: *cgen, t: *node) i32 = { + if (t == nil) { return 1; }; + let direct: i32 = elemsizeof(t); + if (direct != 8) { return direct; }; + let k: nkind = t.kind; + let elem: *node = nil; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return direct; }; + if (elem.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elem.str); + if (ps > 0) { return ps; }; + }; + return slotsize(c, elem); +}; + // nodeisunsigned — best-effort cgen-time inference from the AST. We // don't have a typed AST yet, so we walk surface nodes: // nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet) @@ -986,9 +1029,20 @@ fn slotsize(c: *cgen, typn: *node) i32 = { if (ps > 0) { esz = ps; } else { // Named struct / aliased type: size off - // the structinfo if present. + // the structinfo if present, else follow + // the alias via aliaslookup so + // `[N]formattable` reads the resolved + // tagged slot (e.g. 24B for + // `(i64|str|bool)`), not the fall- + // through 8B. let si: *structinfo = structlookup(c, en); - if (si != nil) { esz = si.totsize; }; + if (si != nil) { esz = si.totsize; } + else { if (c != nil) { + let al: *node = aliaslookup(c, en); + if (al != nil) { + esz = slotsize(c, al); + }; + }; }; }; } else { if (elemn.kind == nkind.N_TTAGGED) { // Tagged-union element: full slot (8 tag + @@ -1734,6 +1788,53 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitline("(BP)\n"); return; }; + // `expr: TaggedAlias` where the cast's destination IS the union + // itself is a widening, not a re-interpret. cgexpr on a CAST + // produces the inner's register shape (str: AX=ptr, BX=len), not + // the tagged AX/DX/CX triple — so peel to the inner and route + // through the matching concrete-variant branch below. A cast to + // a concrete variant (`7: i32`) is left intact so the existing + // scalar / str / slice branches pick the right variant tag. + if (src != nil) { + if (src.kind == nkind.N_CAST) { + if (src.lhs != nil) { + let inner: *node = src.lhs; + let inneristagged: bool = false; + if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + inneristagged = istaggedtype(c, lc.tnode); + }; + }; + if (rhstaggedabicall(c, inner)) { + inneristagged = true; + }; + // Cast's destination = the dst tagged union + // itself? The rhs of N_CAST holds the target + // type. Compare nominally via str match on + // the tagged-alias name. + let castisdst: bool = false; + let castrhs: *node = src.rhs; + if (castrhs != nil) { + if (castrhs.kind == nkind.N_TTAGGED) { + castisdst = true; + }; + if (castrhs.kind == nkind.N_TNAME) { + if (dst != nil) { + if (dst.kind == nkind.N_TNAME) { + if (streq(castrhs.str, dst.str)) { + castisdst = true; + }; + }; + }; + }; + }; + if (castisdst && !inneristagged) { + src = inner; + }; + }; + }; + }; // Tagged source ident: byte-copy slot words then tag-remap. let st: *node = rhstaggedident(c, src); if (st != nil) { @@ -1763,8 +1864,9 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: cgwidentagremap(c, dt, st, slot_off); return; }; - // Tagged source via AX/DX/CX register ABI (N_CALL, N_INDEX of - // tagged element). + // Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX + // of tagged element). R8 carries the 4th word for slice-payload + // variants (slot 32B). if (rhstaggedabicall(c, src)) { cgexpr(c, src); emitline("\tMOVQ\tAX, "); @@ -1780,6 +1882,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitoff((slot_off + 16): i64); emitline("(BP)\n"); }; + if (slot_sz > 24) { + emitline("\tMOVQ\tR8, "); + emitoff((slot_off + 24): i64); + emitline("(BP)\n"); + }; return; }; // Struct payload (literal or ident). @@ -1898,6 +2005,28 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitline("(BP)\n"); return; }; + // Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, CX=cap). + // Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, [+24]=cap. + if (nodeisslice(c, src)) { + cgexpr(c, src); + emitline("\tMOVQ\tAX, "); + emitoff((slot_off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((slot_off + 16): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((slot_off + 24): i64); + emitline("(BP)\n"); + let tag: i32 = taggedvariantindex(c, dt, src); + if (tag < 0) { tag = 0; }; + emitline("\tMOVQ\t$"); + emitint(tag: i64); + emitline(", "); + emitoff(slot_off: i64); + emitline("(BP)\n"); + return; + }; // Scalar payload. cgexpr(c, src); emitline("\tMOVQ\tAX, "); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e170a90e..f385d3b9 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -377,53 +377,46 @@ export fn hassuffix(s: str, suf: str) bool = { return true; }; -// indexbyte — first byte position of byte `c` in `s`. Mirrors -// Hare's strings::byteindex when the needle is a single ASCII rune, -// renamed to match bytes.indexbyte and to disambiguate from Hare's -// `byteindex(haystack, needle: (str | rune))` which we don't have -// the union-arg ABI for yet. -export fn indexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = 0; - for (i < s.len) { - if (s[i] == c) { return i; }; - i += 1; - }; - return; -}; - -// rindexbyte — last byte position of byte `c` in `s`. -export fn rindexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = s.len - 1; - for (i >= 0) { - if (s[i] == c) { return i; }; - i -= 1; - }; - return; -}; - -// index — first index of `sub` in `s`. Naive scan; fine for short -// patterns and small strings, which dominate config and CLI parsing. -// Empty `sub` matches at 0. -export fn index(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return; }; - let last: i32 = s.len - sub.len; - let i: i32 = 0; - for (i <= last) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// byteindex — first byte position of `needle` in `s`. Mirrors Hare's +// strings::byteindex: a single-codepoint rune scans for the byte that +// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), +// a str needle scans for the substring. Returns void if absent. +export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = 0; + for (i < s.len) { + if (s[i] == c) { return i; }; + i += 1; }; - if (ok) { return i; }; - i += 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return 0; }; + if (sub.len > s.len) { return; }; + let last: i32 = s.len - sub.len; + let i: i32 = 0; + for (i <= last) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i += 1; + }; + return; + }; }; return; }; +// contains — true iff `sub` appears in `s`. Mirrors Hare's +// strings::contains shape (byte-wise on the str-needle case). export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = index(s, sub); + let r: (i32 | void) = byteindex(s, sub); match (r) { case let i: i32 => return true; case void => return false; @@ -467,21 +460,37 @@ export fn dup(s: str) str = { return r; }; -// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex -// (slice case). Empty `sub` matches at s.len. -export fn rindex(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return s.len; }; - if (sub.len > s.len) { return; }; - let i: i32 = s.len - sub.len; - for (i >= 0) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's +// strings::rbyteindex. Rune needle scans for the byte that encodes it +// (ASCII only); str needle scans for the substring. Empty str needle +// matches at s.len. +export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = s.len - 1; + for (i >= 0) { + if (s[i] == c) { return i; }; + i -= 1; }; - if (ok) { return i; }; - i -= 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return s.len; }; + if (sub.len > s.len) { return; }; + let i: i32 = s.len - sub.len; + for (i >= 0) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i -= 1; + }; + return; + }; }; return; }; @@ -558,12 +567,11 @@ export fn trimbyte(s: str, c: u8) str = { // MODULE: strconv // strconv — number↔string conversions. // -// Mirrors Hare's strconv:: surface. The *tos functions return a fresh -// owned `str`; release via os.free(r.ptr, r.len: u64) when done. -// Hare returns `const str` into a static buffer; ww allocates per -// call because the wwstage cgen doesn't currently support mutating a -// module-level `*u8` (so a lazy-init shared buffer isn't expressible -// today). Graduate to the static-buffer shape once that lands. +// Mirrors Hare's strconv:: surface. The *tos functions return a +// `const str` view into a module-level buffer that is overwritten on +// the next call to the same function; callers must copy the bytes if +// they need to outlive the next invocation. See [[strings.dup]] to +// duplicate. Matches Hare's strconv::*tos semantics. use os; use strings; @@ -580,43 +588,44 @@ export type overflow = !void; // error — any error from a strconv call. Mirrors Hare's strconv::error. export type error = !(invalid | overflow); -// base — numeric base for parsing/formatting. Plain i32 (not a named -// enum) because cross-module `strconv.base.DEC` chains miscompile in -// the cstage cgen — it emits a memory load through `base(SB)` rather -// than inlining the enum value. Hare names them as `strconv::base` -// enum values; we expose them as module-level `def`s so callers say -// `strconv.DEC` and the cgen inlines the immediate. +// base — numeric base for parsing/formatting. Mirrors Hare's +// `strconv::base` (Hare uses `enum uint`; we pick `enum i32` since +// the underlying parse/format loops index with i32). // -// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces -// lowercase a-f digits. -export def DEFAULT: i32 = 0; -export def BIN: i32 = 2; -export def OCT: i32 = 8; -export def DEC: i32 = 10; -export def HEX_UPPER: i32 = 16; -export def HEX: i32 = 16; -export def HEX_LOWER: i32 = 17; +// HEX is an alias for HEX_UPPER; HEX_LOWER is a pseudo-base that +// produces lowercase a-f digits. +export type base = enum i32 { + DEFAULT = 0, + BIN = 2, + OCT = 8, + DEC = 10, + HEX_UPPER = 16, + HEX = 16, + HEX_LOWER = 17, +}; -fn basenum(b: i32) i64 = { - if (b == BIN) { return 2; }; - if (b == OCT) { return 8; }; - if (b == HEX) { return 16; }; - if (b == HEX_UPPER) { return 16; }; - if (b == HEX_LOWER) { return 16; }; +fn basenum(b: base) i64 = { + if (b == base.BIN) { return 2; }; + if (b == base.OCT) { return 8; }; + if (b == base.HEX) { return 16; }; + if (b == base.HEX_UPPER) { return 16; }; + if (b == base.HEX_LOWER) { return 16; }; return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: i32) u8 = { +fn basedigit(d: i64, b: base) u8 = { if (d < 10) { return (d + 48): u8; }; let off: i64 = d - 10; - if (b == HEX_LOWER) { return (off + 97): u8; }; + if (b == base.HEX_LOWER) { return (off + 97): u8; }; return (off + 65): u8; }; -// u64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free(r.ptr, r.len: u64). Mirrors Hare's -// strconv::u64tos (Hare returns const str into a static buffer). -export fn u64tos(v: u64, b: i32) str = { +// u64tos — convert v to a base-b numeric string. Returns a view into +// `u64tos_buf` which is overwritten on the next call. Matches Hare's +// strconv::u64tos. +let u64tos_buf: [65]u8; + +export fn u64tos(v: u64, b: base) str = { let nb: u64 = basenum(b): u64; let tmp: [65]u8; let i: i32 = 0; @@ -628,22 +637,25 @@ export fn u64tos(v: u64, b: i32) str = { n = n / nb; i += 1; }; - let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; - buf[out] = tmp[i]; + u64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &u64tos_buf[0]; r.len = out; return r; }; -// i64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free. Mirrors Hare's strconv::i64tos. -export fn i64tos(v: i64, b: i32) str = { +// i64tos — convert v to a base-b numeric string. Returns a view into +// `i64tos_buf` which is overwritten on the next call. Independent +// buffer from u64tos so i64tos's own call to u64tos doesn't clobber +// the in-flight result. Matches Hare's strconv::i64tos. +let i64tos_buf: [66]u8; + +export fn i64tos(v: i64, b: base) str = { let neg: bool = false; let n: i64 = v; if (n < 0) { neg = true; n = -n; }; @@ -657,37 +669,33 @@ export fn i64tos(v: i64, b: i32) str = { n = n / nb; i += 1; }; - let extra: i32 = 0; - if (neg) { extra = 1; }; - let total: i32 = i + extra; - let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { buf[out] = 45u8; out += 1; }; // '-' + if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; - buf[out] = tmp[i]; + i64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &i64tos_buf[0]; r.len = out; return r; }; -export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; -export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; -export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; +export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; -export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; -export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; -export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; +export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; // digval — value of digit byte `c` under base `b`, or -1 if not a // valid digit. Letters are accepted case-insensitively under HEX / // HEX_UPPER; only lowercase under HEX_LOWER. -fn digval(c: u8, b: i32) i32 = { +fn digval(c: u8, b: base) i32 = { if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; - if (b == HEX_LOWER) { + if (b == base.HEX_LOWER) { if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; return -1; }; @@ -700,7 +708,7 @@ fn digval(c: u8, b: i32) i32 = { // No locale, no whitespace, no underscores: optional leading '-' then // digits. Returns invalid with the offending index or overflow on // out-of-range. -export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { +export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; @@ -721,7 +729,7 @@ export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { }; // stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. -export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { +export fn stou64(s: str, b: base) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let nb: u64 = basenum(b): u64; let v: u64 = 0u64; @@ -737,7 +745,7 @@ export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { return v; }; -export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { +export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -751,7 +759,7 @@ export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { return 0: invalid; // unreachable; appeases the path-cov checker }; -export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { +export fn stoi16(s: str, b: base) (i16 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -765,7 +773,7 @@ export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { return 0: invalid; }; -export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { +export fn stoi8(s: str, b: base) (i8 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -779,7 +787,7 @@ export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { return 0: invalid; }; -export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { +export fn stou32(s: str, b: base) (u32 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -792,7 +800,7 @@ export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { return 0: invalid; }; -export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { +export fn stou16(s: str, b: base) (u16 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -805,7 +813,7 @@ export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { return 0: invalid; }; -export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { +export fn stou8(s: str, b: base) (u8 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -841,13 +849,14 @@ export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. +let f64tos_buf: [64]u8; + export fn f64tos(v: f64) str = { - let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - tmp[out] = 45u8; // '-' + f64tos_buf[out] = 45u8; // '-' out += 1; f = -f; }; @@ -857,12 +866,9 @@ export fn f64tos(v: f64) str = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; + for (k < s.len) { f64tos_buf[out] = s[k]; out += 1; k += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -881,32 +887,27 @@ export fn f64tos(v: f64) str = { ip += 1; fp = 0; }; - let intstr: str = i64tos(ip, DEC); + let intstr: str = i64tos(ip, base.DEC); let k: i32 = 0; - for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; - os.free(intstr.ptr: *void, intstr.len: u64); + for (k < intstr.len) { f64tos_buf[out] = intstr.ptr[k]; out += 1; k += 1; }; if (fp != 0) { - tmp[out] = 46u8; // '.' + f64tos_buf[out] = 46u8; // '.' out += 1; - let fracstr: str = u64tos(fp: u64, DEC); + let fracstr: str = u64tos(fp: u64, base.DEC); // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → // fp=50000, fracstr="50000", pad one '0' before). let z: i32 = 6 - fracstr.len; - for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + for (z > 0) { f64tos_buf[out] = 48u8; out += 1; z -= 1; }; k = 0; - for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; - os.free(fracstr.ptr: *void, fracstr.len: u64); + for (k < fracstr.len) { f64tos_buf[out] = fracstr.ptr[k]; out += 1; k += 1; }; // Trim trailing zeros in the fractional part. for (out > 0) { - if (tmp[out - 1] != 48u8) { break; }; + if (f64tos_buf[out - 1] != 48u8) { break; }; out -= 1; }; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -1307,10 +1308,10 @@ export fn tokprint(fd: i32, t: *tok) void = { fputsstr(fd, ""); }; fputcbyte(fd, 58u8); // ':' - let ls: str = strconv.i64tos(t.line: i64, strconv.DEC); + let ls: str = strconv.i64tos(t.line: i64, strconv.base.DEC); os.write(fd, ls.ptr, ls.len: u64); fputcbyte(fd, 58u8); - let cs: str = strconv.i64tos(t.col: i64, strconv.DEC); + let cs: str = strconv.i64tos(t.col: i64, strconv.base.DEC); os.write(fd, cs.ptr, cs.len: u64); fputcbyte(fd, 32u8); // ' ' fputsstr(fd, tokname(t.kind)); @@ -1326,11 +1327,11 @@ export fn tokprint(fd: i32, t: *tok) void = { fputq(fd, ttext.ptr, ttext.len); } else { if (t.kind == tkind.TK_INT) { fputcbyte(fd, 32u8); - let us: str = strconv.u64tos(t.uval, strconv.DEC); + let us: str = strconv.u64tos(t.uval, strconv.base.DEC); os.write(fd, us.ptr, us.len: u64); } else { if (t.kind == tkind.TK_RUNE) { fputcbyte(fd, 32u8); - let us: str = strconv.u64tos(t.uval, strconv.DEC); + let us: str = strconv.u64tos(t.uval, strconv.base.DEC); os.write(fd, us.ptr, us.len: u64); };};};};}; // tkind.TK_FLOAT is intentionally not handled here — %g formatting @@ -2572,11 +2573,11 @@ fn pr(fd: i32, n: *node, d: i32) void = { if (n.kind == nkind.N_INTLIT) { putc1(fd, 32u8); - let s: str = strconv.u64tos(n.uval, strconv.DEC); + let s: str = strconv.u64tos(n.uval, strconv.base.DEC); os.write(fd, s.ptr, s.len: u64); } else { if (n.kind == nkind.N_RUNELIT) { putc1(fd, 32u8); - let s: str = strconv.u64tos(n.uval, strconv.DEC); + let s: str = strconv.u64tos(n.uval, strconv.base.DEC); os.write(fd, s.ptr, s.len: u64); } else { if ( n.kind == nkind.N_STRLIT || @@ -5648,7 +5649,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { return rest + widensz / 8; }; cgexpr(c, arg); - if (nodeisstr(c, arg)) { + if (nodeisslice(c, arg)) { + // Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, + // CX=cap). Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, + // [+24]=cap. Push high→low so pop drains tag first. + // Requires widensz >= 32; a smaller slot would mean the + // destination union doesn't list slice as a variant + // (caller should have flagged a type error). + emitline("\tPUSHQ\tCX\n"); + emitline("\tPUSHQ\tBX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tMOVQ\t$"); + emitint(widentag: i64); + emitline(", AX\n"); + emitline("\tPUSHQ\tAX\n"); + } else { 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"); @@ -5661,16 +5676,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { // 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) { + let pp: i32 = widensz - 8; + for (pp > 8) { emitline("\tXORQ\tDX, DX\n"); emitline("\tPUSHQ\tDX\n"); + pp -= 8; }; 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 @@ -5756,25 +5773,28 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { // Slice/tagged ident args: emit per-register MOVQ+PUSHQ pairs in // reverse order (cap/v1, len/v0, ptr/tag) so a left-to-right pop // into argregs lands the canonical (ptr/tag, len/v0, cap/v1). + // For tagged ident with a >24B slot (slice-payload variant), + // push a fourth word from off+24. if (arg.kind == nkind.N_IDENT) { let nm: str = arg.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let off: i32 = lc.off; if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 16): i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - emitline("\tPUSHQ\tAX\n"); - return rest + 3; + let nwords: i32 = 3; + if (istaggedtype(c, lc.tnode)) { + let ssz: i32 = slotsize(c, lc.tnode); + nwords = ssz / 8; + }; + let w: i32 = nwords - 1; + for (w >= 0) { + emitline("\tMOVQ\t"); + emitoff((off + w*8): i64); + emitline("(BP), AX\n"); + emitline("\tPUSHQ\tAX\n"); + w -= 1; + }; + return rest + nwords; }; }; }; @@ -6157,6 +6177,9 @@ fn dotinnerstructptr(c: *cgen, n: *node) *node = { // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). +// For aliased element types (e.g. `[N]formattable`), callers that +// need the resolved slot size should use elemsizeofc(c, t) which +// follows aliases via slotsize. fn elemsizeof(t: *node) i32 = { if (t == nil) { return 1; }; let k: nkind = t.kind; @@ -6183,6 +6206,27 @@ fn elemsizeof(t: *node) i32 = { return 8; }; +// elemsizeofc — like elemsizeof but resolves aliased element types +// (struct / tagged / `type foo = bar;`) via slotsize. Used where +// cgindex / cgassign need a correct stride for `[N]Alias` arrays +// whose Alias resolves to a tagged union (e.g. `[N]formattable`). +fn elemsizeofc(c: *cgen, t: *node) i32 = { + if (t == nil) { return 1; }; + let direct: i32 = elemsizeof(t); + if (direct != 8) { return direct; }; + let k: nkind = t.kind; + let elem: *node = nil; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return direct; }; + if (elem.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elem.str); + if (ps > 0) { return ps; }; + }; + return slotsize(c, elem); +}; + // nodeisunsigned — best-effort cgen-time inference from the AST. We // don't have a typed AST yet, so we walk surface nodes: // nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet) @@ -6533,9 +6577,20 @@ fn slotsize(c: *cgen, typn: *node) i32 = { if (ps > 0) { esz = ps; } else { // Named struct / aliased type: size off - // the structinfo if present. + // the structinfo if present, else follow + // the alias via aliaslookup so + // `[N]formattable` reads the resolved + // tagged slot (e.g. 24B for + // `(i64|str|bool)`), not the fall- + // through 8B. let si: *structinfo = structlookup(c, en); - if (si != nil) { esz = si.totsize; }; + if (si != nil) { esz = si.totsize; } + else { if (c != nil) { + let al: *node = aliaslookup(c, en); + if (al != nil) { + esz = slotsize(c, al); + }; + }; }; }; } else { if (elemn.kind == nkind.N_TTAGGED) { // Tagged-union element: full slot (8 tag + @@ -7281,6 +7336,53 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitline("(BP)\n"); return; }; + // `expr: TaggedAlias` where the cast's destination IS the union + // itself is a widening, not a re-interpret. cgexpr on a CAST + // produces the inner's register shape (str: AX=ptr, BX=len), not + // the tagged AX/DX/CX triple — so peel to the inner and route + // through the matching concrete-variant branch below. A cast to + // a concrete variant (`7: i32`) is left intact so the existing + // scalar / str / slice branches pick the right variant tag. + if (src != nil) { + if (src.kind == nkind.N_CAST) { + if (src.lhs != nil) { + let inner: *node = src.lhs; + let inneristagged: bool = false; + if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + inneristagged = istaggedtype(c, lc.tnode); + }; + }; + if (rhstaggedabicall(c, inner)) { + inneristagged = true; + }; + // Cast's destination = the dst tagged union + // itself? The rhs of N_CAST holds the target + // type. Compare nominally via str match on + // the tagged-alias name. + let castisdst: bool = false; + let castrhs: *node = src.rhs; + if (castrhs != nil) { + if (castrhs.kind == nkind.N_TTAGGED) { + castisdst = true; + }; + if (castrhs.kind == nkind.N_TNAME) { + if (dst != nil) { + if (dst.kind == nkind.N_TNAME) { + if (streq(castrhs.str, dst.str)) { + castisdst = true; + }; + }; + }; + }; + }; + if (castisdst && !inneristagged) { + src = inner; + }; + }; + }; + }; // Tagged source ident: byte-copy slot words then tag-remap. let st: *node = rhstaggedident(c, src); if (st != nil) { @@ -7310,8 +7412,9 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: cgwidentagremap(c, dt, st, slot_off); return; }; - // Tagged source via AX/DX/CX register ABI (N_CALL, N_INDEX of - // tagged element). + // Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX + // of tagged element). R8 carries the 4th word for slice-payload + // variants (slot 32B). if (rhstaggedabicall(c, src)) { cgexpr(c, src); emitline("\tMOVQ\tAX, "); @@ -7327,6 +7430,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitoff((slot_off + 16): i64); emitline("(BP)\n"); }; + if (slot_sz > 24) { + emitline("\tMOVQ\tR8, "); + emitoff((slot_off + 24): i64); + emitline("(BP)\n"); + }; return; }; // Struct payload (literal or ident). @@ -7445,6 +7553,28 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitline("(BP)\n"); return; }; + // Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, CX=cap). + // Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, [+24]=cap. + if (nodeisslice(c, src)) { + cgexpr(c, src); + emitline("\tMOVQ\tAX, "); + emitoff((slot_off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((slot_off + 16): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((slot_off + 24): i64); + emitline("(BP)\n"); + let tag: i32 = taggedvariantindex(c, dt, src); + if (tag < 0) { tag = 0; }; + emitline("\tMOVQ\t$"); + emitint(tag: i64); + emitline(", "); + emitoff(slot_off: i64); + emitline("(BP)\n"); + return; + }; // Scalar payload. cgexpr(c, src); emitline("\tMOVQ\tAX, "); @@ -7997,7 +8127,7 @@ fn cgindex(c: *cgen, n: *node) void = { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); + esz = elemsizeofc(c, baselocal.tnode); signed_elem = elemissigned(baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); @@ -8005,13 +8135,13 @@ fn cgindex(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; }; @@ -8076,6 +8206,9 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { + if (elem_slot_sz > 24) { + emitline("\tMOVQ\t24(BX), R8\n"); + }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; @@ -8114,6 +8247,9 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { + if (elem_slot_sz > 24) { + emitline("\tMOVQ\t24(BX), R8\n"); + }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; @@ -8272,7 +8408,7 @@ fn cgmatch(c: *cgen, n: *node) void = { }; } else { // Non-ident scrutinee (call result, arr[i], ?, etc.). - // Spill into a 24B `@match_spill` scratch slot and + // Spill into an `@match_spill` scratch slot and // dispatch off it. Tagged returns (N_CALL) follow the // AX:DX:CX convention; tagged-element loads (N_INDEX) // after the cgindex fix produce the same triple. @@ -8327,6 +8463,16 @@ fn cgmatch(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, "); emitoff((scrutoff + 16): i64); emitline("(BP)\n"); + // R8 carries the 4th return word when the + // scrutinee's tagged union has a slice-payload + // variant (slot 32B). Harmless for narrower + // returns — R8 is callee-clobbered either way. + let ssz: i32 = slotsize(c, scrutt); + if (ssz > 24) { + emitline("\tMOVQ\tR8, "); + emitoff((scrutoff + 24): i64); + emitline("(BP)\n"); + }; }; }; }; @@ -8412,25 +8558,22 @@ fn cgmatch(c: *cgen, n: *node) void = { }; } else { let bsz: i32 = 8; - if (isstrtype(c, pat)) { bsz = 16; }; + if (isstrtype(c, pat)) { bsz = 16; } + else { if (isslicetype(c, pat)) { bsz = 24; }; }; // localalloc (not localadd): match-arm // binds don't dedup with same-named binds // in *other* matches, since C's cgexpr // allocates a fresh slot per match expr. let voff: i32 = localalloc(c, bn, bsz, pat); - emitline("\tMOVQ\t"); - emitoff((scrutoff + 8): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(voff: i64); - emitline("(BP)\n"); - if (bsz == 16) { + let bw: i32 = 0; + for (bw < bsz) { emitline("\tMOVQ\t"); - emitoff((scrutoff + 16): i64); + emitoff((scrutoff + 8 + bw): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); - emitoff((voff + 8): i64); + emitoff((voff + bw): i64); emitline("(BP)\n"); + bw += 8; }; }; }; @@ -9052,6 +9195,20 @@ fn cgdot(c: *cgen, n: *node) void = { };}; }; }; + // Nested module-qualified field where the chain didn't fold to a + // known shape (raw w6c on a single file with `use mod;` but no + // driver concatenation — the inner enum / struct hasn't been + // seen). Emit `MOVQ (SB), AX` so the linker surfaces a + // clean undefined-symbol error on the leaf. Mirror of + // cmd/w6c/cgen.c N_DOT nested fallback. + if (lhs != nil) { + if (lhs.kind == nkind.N_DOT) { + emitline("\tMOVQ\t"); + emitsymname(c, fld); + emitline("(SB), AX\n"); + return; + }; + }; return; }; @@ -9080,10 +9237,9 @@ fn cgun(c: *cgen, n: *node) void = { emitline("\t"); emitline(sub); emitline("\tX1, X0\n"); return; }; - cgexpr(c, n.lhs); - if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; - if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; - if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; + // Address-of has its own evaluation strategy — we want the address + // of the operand, not its value. Special-case here so `&arr[i]` + // doesn't compile the value load and then discard it. if (n.op == tkind.TK_AMP) { let opnd: *node = n.lhs; if (opnd != nil) { @@ -9096,17 +9252,94 @@ fn cgun(c: *cgen, n: *node) void = { emitline("(BP), AX\n"); return; }; - // Top-level mutable let — RIP-relative LEAQ. if (isletvar(c, nm)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; + return; + }; + if (opnd.kind == nkind.N_INDEX) { + // &base[i] = base + i*esz, no dereference. + let base: *node = opnd.lhs; + let idx: *node = opnd.rhs; + let esz: i32 = 8; + let isglobalarr: bool = false; + let isglobalptr: bool = false; + let globalname: str; + globalname.ptr = nil; globalname.len = 0; + let baselocal: *local = nil; + let isarr: bool = false; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + baselocal = localfindnode(c, base.str); + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + let tn: *node = baselocal.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { isarr = true; }; + }; + } else { + let tn: *node = letvartnode(c, base.str); + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + isglobalarr = true; + globalname = base.str; + esz = elemsizeofc(c, tn); + }; + if (tn.kind == nkind.N_TPTR) { + isglobalptr = true; + globalname = base.str; + esz = elemsizeofc(c, tn); + }; + }; + }; + }; + }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { + if (isarr) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { + // Complex base: spill scaled idx, eval + // base to AX, move to BX, restore idx. + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + emitline("\tPOPQ\tAX\n"); + };};}; + emitline("\tADDQ\tBX, AX\n"); + return; }; }; return; }; + cgexpr(c, n.lhs); + if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; + if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; + if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; if (n.op == tkind.TK_NOT) { let t: str = mklabel(c, "tt"); let e: str = mklabel(c, "te"); @@ -9812,7 +10045,7 @@ fn cgassign(c: *cgen, n: *node) void = { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); + esz = elemsizeofc(c, baselocal.tnode); let btn: *node = baselocal.tnode; if (btn != nil) { let bk: nkind = btn.kind; @@ -9826,13 +10059,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; - esz = elemsizeof(tn); + esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; }; @@ -10526,16 +10759,55 @@ fn cgassign(c: *cgen, n: *node) void = { lvf = lvf.lvnext; }; }; - if (isfg && n.op == tkind.TK_ASSIGN) { + if (isfg) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; - if (isf32g) { mov = "MOVSS"; }; + let addf: str = "ADDSD"; + let subf: str = "SUBSD"; + let mulf: str = "MULSD"; + let divf: str = "DIVSD"; + if (isf32g) { + mov = "MOVSS"; + addf = "ADDSS"; + subf = "SUBSS"; + mulf = "MULSS"; + divf = "DIVSS"; + }; emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); + if (n.op == tkind.TK_ASSIGN) { + emitline("\t"); + emitline(mov); + emitline("\tX0, (CX)\n"); + return; + }; + // Compound: X1 = load; X1 OP= X0; store X1. + // ADDSD/SUBSD/MULSD/DIVSD are register-register + // only, so we can't combine direct to memory. + let fop: str; + fop.ptr = nil; fop.len = 0; + if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; + if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; + if (n.op == tkind.TK_STAREQ) { fop = mulf; }; + if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; + if (fop.len == 0) { + // Unsupported (e.g., %= on float): + // fall back to plain store of rhs. + emitline("\t"); + emitline(mov); + emitline("\tX0, (CX)\n"); + return; + }; emitline("\t"); emitline(mov); - emitline("\tX0, (CX)\n"); + emitline("\t(CX), X1\n"); + emitline("\t"); + emitline(fop); + emitline("\tX0, X1\n"); + emitline("\t"); + emitline(mov); + emitline("\tX1, (CX)\n"); return; }; cgexpr(c, n.rhs); @@ -10614,15 +10886,56 @@ fn cgassign(c: *cgen, n: *node) void = { lcf32 = isf32type(c, lcn.tnode); }; // Float-typed local: rhs lands in X0; store via MOVSD/ - // MOVSS, no AX shuffle. Only plain `=` is wired; compound - // float-assign isn't. - if (lcf && n.op == tkind.TK_ASSIGN) { + // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads + // slot into X1, combines into X1, stores X1 back — + // ADDSD/SUBSD/MULSD/DIVSD are register-register only. + if (lcf) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; - if (lcf32) { mov = "MOVSS"; }; + let addf: str = "ADDSD"; + let subf: str = "SUBSD"; + let mulf: str = "MULSD"; + let divf: str = "DIVSD"; + if (lcf32) { + mov = "MOVSS"; + addf = "ADDSS"; + subf = "SUBSS"; + mulf = "MULSS"; + divf = "DIVSS"; + }; + if (n.op == tkind.TK_ASSIGN) { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + let fop: str; + fop.ptr = nil; fop.len = 0; + if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; + if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; + if (n.op == tkind.TK_STAREQ) { fop = mulf; }; + if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; + if (fop.len == 0) { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; emitline("\t"); emitline(mov); - emitline("\tX0, "); + emitline("\t"); + emitoff(off: i64); + emitline("(BP), X1\n"); + emitline("\t"); + emitline(fop); + emitline("\tX0, X1\n"); + emitline("\t"); + emitline(mov); + emitline("\tX1, "); emitoff(off: i64); emitline("(BP)\n"); return; @@ -10889,6 +11202,11 @@ fn cgreturn(c: *cgen, n: *node) void = { emitoff((scroff + 16): i64); emitline("(BP), CX\n"); }; + if (rsz > 24) { + emitline("\tMOVQ\t"); + emitoff((scroff + 24): i64); + emitline("(BP), R8\n"); + }; emitline("\tMOVQ\tBP, SP\n"); emitline("\tPOPQ\tBP\n"); emitline("\tRET\n"); @@ -10911,12 +11229,19 @@ fn cgreturn(c: *cgen, n: *node) void = { return; }; let idx: i32 = taggedvariantindex(c, c.fnret, rhs); - if (nodeisstr(c, rhs)) { + if (nodeisslice(c, rhs)) { + // cgexpr leaves (AX=ptr, BX=len, CX=cap). + // Shuffle into return ABI: DX=ptr, CX=len, + // R8=cap. + emitline("\tMOVQ\tCX, R8\n"); + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tMOVQ\tAX, DX\n"); + } else { if (nodeisstr(c, rhs)) { emitline("\tMOVQ\tBX, CX\n"); emitline("\tMOVQ\tAX, DX\n"); } else { emitline("\tMOVQ\tAX, DX\n"); - }; + };}; emitline("\tMOVQ\t$"); if (idx < 0) { idx = 0; }; emitint(idx: i64); @@ -11928,10 +12253,24 @@ fn scanlocals(c: *cgen, n: *node) i32 = { if (bn.len > 0) { let pat: *node = n.lhs; if (pat != nil) { - if (isstrtype(c, pat)) { total += 16; } - else { total += 8; }; + if (isstrtype(c, pat)) { total += 16; } + else { if (isslicetype(c, pat)) { total += 24; } + else { total += 8; }; }; }; }; + // Match arms get a fresh local scope at emission time + // (cgmatch saves c.locals before each arm and restores + // after). scanlocals must mirror that: walk the arm + // body with a saved/restored seenmark set so two arms + // declaring the same name each get their own slot, + // matching the per-arm frame growth the emit phase + // produces. + if (n.body != nil) { + let saved: *local = c.locals; + total += scanlocals(c, n.body); + c.locals = saved; + }; + return total; }; // Tagged-arr/slice index store needs a 24B scratch slot // (`@tagscr`) for cgwidentaggedstore to materialise the source @@ -12124,10 +12463,36 @@ fn cgfnparams(c: *cgen, params: *node) void = { idx += 1; w += 1; }; + } else { if (idx < 6 && nw > 1) { + // Partial fit: fill remaining regs, then read + // the tail from positive BP offsets. Mirrors + // the caller's greedy reg fill in pushargsrev. + let off: i32 = localadd(c, nm, slot, p.lhs); + let regs_left: i32 = 6 - idx; + let w: i32 = 0; + for (w < regs_left) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + for (w < nw) { + emitline("\tMOVQ\t"); + emitoff((16 + stkcursor*8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + stkcursor += 1; + w += 1; + }; } else { localaddstack(c, nm, p.lhs, 16 + stkcursor*8); stkcursor += nw; - }; + };}; } else { if (isslicetype(c, p.lhs)) { if (idx + 3 <= 6) { let off: i32 = localadd(c, nm, 24, p.lhs); @@ -12208,7 +12573,12 @@ fn cgfn(c: *cgen, fn_: *node) void = { }; a = a.next; }; - if (!isffi) { + // `main` is the linker entry-point convention; even + // when not marked `export`, it must keep its bare + // name so w6l's _start can resolve `CALL main(SB)`. + // Mirror of cmd/w6c/cgen.c collectmods exemption. + let isentry: bool = streq(fn_.str, "main"); + if (!isffi && !isentry) { os.write(1, fn_.module.ptr, fn_.module.len: u64); os.write(1, ".".ptr, 1u64); }; @@ -12380,6 +12750,26 @@ fn aliaslookup(c: *cgen, name: str) *node = { if (streq(an, name)) { return a.target; }; a = a.aanext; }; + // Module-qualified form: `pkg.alias` → try the bare leaf so a + // cross-module reference resolves the same way bare access does + // after driver concatenation. Mirrors the check.c module- + // qualified type resolution. + let i: i32 = name.len - 1; + for (i >= 0) { + if (name[i] == 46u8) { // '.' + let leaf: str; + leaf.ptr = name.ptr + ((i + 1): u64); + leaf.len = name.len - (i + 1); + let b: *aliasent = c.aliases; + for (b != nil) { + if (streq(b.aname, leaf)) { return b.target; }; + b = b.aanext; + }; + i = -1; + } else { + i -= 1; + }; + }; return nil; }; @@ -12775,12 +13165,12 @@ fn localfind(c: *cgen, name: str) i32 = { fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); }; fn emitint(v: i64) void = { - let s: str = strconv.i64tos(v, strconv.DEC); + let s: str = strconv.i64tos(v, strconv.base.DEC); os.write(1, s.ptr, s.len: u64); }; fn emituint(v: u64) void = { - let s: str = strconv.u64tos(v, strconv.DEC); + let s: str = strconv.u64tos(v, strconv.base.DEC); os.write(1, s.ptr, s.len: u64); }; @@ -12818,7 +13208,7 @@ fn mklabel(c: *cgen, prefix: str) str = { i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; @@ -12857,7 +13247,7 @@ fn mkscratchname(c: *cgen, prefix: str) str = { i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; @@ -12894,7 +13284,7 @@ fn internstrlit(c: *cgen, bytes: str) str = { // New label "_S_". let buf: [32]u8; buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" - let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.DEC); + let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.base.DEC); let n: i32 = ns.len; let dk: i32 = 0; for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; }; @@ -14031,11 +14421,11 @@ export fn main(argc: i32, argv: **u8) i32 = { // ": / resolved" os.write(1, argstr(path).ptr, argstrlen(path): u64); os.write(1, ": ".ptr, 2u64); - let rs: str = strconv.i64tos(ck.nresolved: i64, strconv.DEC); + let rs: str = strconv.i64tos(ck.nresolved: i64, strconv.base.DEC); os.write(1, rs.ptr, rs.len: u64); os.write(1, "/".ptr, 1u64); let total: i32 = ck.nresolved + ck.nunresolved; - let ts: str = strconv.i64tos(total: i64, strconv.DEC); + let ts: str = strconv.i64tos(total: i64, strconv.base.DEC); os.write(1, ts.ptr, ts.len: u64); os.write(1, " resolved\n".ptr, 10u64); if (ck.nunresolved > 0) { return 1; }; diff --git a/selfhost/cmd/wwdump/main.ww b/selfhost/cmd/wwdump/main.ww index f301150c..0fcb205d 100644 --- a/selfhost/cmd/wwdump/main.ww +++ b/selfhost/cmd/wwdump/main.ww @@ -145,11 +145,11 @@ export fn main(argc: i32, argv: **u8) i32 = { // ": / resolved" os.write(1, argstr(path).ptr, argstrlen(path): u64); os.write(1, ": ".ptr, 2u64); - let rs: str = strconv.i64tos(ck.nresolved: i64, strconv.DEC); + let rs: str = strconv.i64tos(ck.nresolved: i64, strconv.base.DEC); os.write(1, rs.ptr, rs.len: u64); os.write(1, "/".ptr, 1u64); let total: i32 = ck.nresolved + ck.nunresolved; - let ts: str = strconv.i64tos(total: i64, strconv.DEC); + let ts: str = strconv.i64tos(total: i64, strconv.base.DEC); os.write(1, ts.ptr, ts.len: u64); os.write(1, " resolved\n".ptr, 10u64); if (ck.nunresolved > 0) { return 1; }; diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 146b2683..70021f7b 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -269,53 +269,46 @@ export fn hassuffix(s: str, suf: str) bool = { return true; }; -// indexbyte — first byte position of byte `c` in `s`. Mirrors -// Hare's strings::byteindex when the needle is a single ASCII rune, -// renamed to match bytes.indexbyte and to disambiguate from Hare's -// `byteindex(haystack, needle: (str | rune))` which we don't have -// the union-arg ABI for yet. -export fn indexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = 0; - for (i < s.len) { - if (s[i] == c) { return i; }; - i += 1; - }; - return; -}; - -// rindexbyte — last byte position of byte `c` in `s`. -export fn rindexbyte(s: str, c: u8) (i32 | void) = { - let i: i32 = s.len - 1; - for (i >= 0) { - if (s[i] == c) { return i; }; - i -= 1; - }; - return; -}; - -// index — first index of `sub` in `s`. Naive scan; fine for short -// patterns and small strings, which dominate config and CLI parsing. -// Empty `sub` matches at 0. -export fn index(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return; }; - let last: i32 = s.len - sub.len; - let i: i32 = 0; - for (i <= last) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// byteindex — first byte position of `needle` in `s`. Mirrors Hare's +// strings::byteindex: a single-codepoint rune scans for the byte that +// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), +// a str needle scans for the substring. Returns void if absent. +export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = 0; + for (i < s.len) { + if (s[i] == c) { return i; }; + i += 1; }; - if (ok) { return i; }; - i += 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return 0; }; + if (sub.len > s.len) { return; }; + let last: i32 = s.len - sub.len; + let i: i32 = 0; + for (i <= last) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i += 1; + }; + return; + }; }; return; }; +// contains — true iff `sub` appears in `s`. Mirrors Hare's +// strings::contains shape (byte-wise on the str-needle case). export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = index(s, sub); + let r: (i32 | void) = byteindex(s, sub); match (r) { case let i: i32 => return true; case void => return false; @@ -359,21 +352,37 @@ export fn dup(s: str) str = { return r; }; -// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex -// (slice case). Empty `sub` matches at s.len. -export fn rindex(s: str, sub: str) (i32 | void) = { - if (sub.len == 0) { return s.len; }; - if (sub.len > s.len) { return; }; - let i: i32 = s.len - sub.len; - for (i >= 0) { - let j: i32 = 0; - let ok: bool = true; - for (j < sub.len) { - if (s[i + j] != sub[j]) { ok = false; j = sub.len; } - else { j += 1; }; +// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's +// strings::rbyteindex. Rune needle scans for the byte that encodes it +// (ASCII only); str needle scans for the substring. Empty str needle +// matches at s.len. +export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { + match (needle) { + case let r: rune => { + let c: u8 = r: u8; + let i: i32 = s.len - 1; + for (i >= 0) { + if (s[i] == c) { return i; }; + i -= 1; }; - if (ok) { return i; }; - i -= 1; + return; + }; + case let sub: str => { + if (sub.len == 0) { return s.len; }; + if (sub.len > s.len) { return; }; + let i: i32 = s.len - sub.len; + for (i >= 0) { + let j: i32 = 0; + let ok: bool = true; + for (j < sub.len) { + if (s[i + j] != sub[j]) { ok = false; j = sub.len; } + else { j += 1; }; + }; + if (ok) { return i; }; + i -= 1; + }; + return; + }; }; return; }; @@ -450,12 +459,11 @@ export fn trimbyte(s: str, c: u8) str = { // MODULE: strconv // strconv — number↔string conversions. // -// Mirrors Hare's strconv:: surface. The *tos functions return a fresh -// owned `str`; release via os.free(r.ptr, r.len: u64) when done. -// Hare returns `const str` into a static buffer; ww allocates per -// call because the wwstage cgen doesn't currently support mutating a -// module-level `*u8` (so a lazy-init shared buffer isn't expressible -// today). Graduate to the static-buffer shape once that lands. +// Mirrors Hare's strconv:: surface. The *tos functions return a +// `const str` view into a module-level buffer that is overwritten on +// the next call to the same function; callers must copy the bytes if +// they need to outlive the next invocation. See [[strings.dup]] to +// duplicate. Matches Hare's strconv::*tos semantics. use os; use strings; @@ -472,43 +480,44 @@ export type overflow = !void; // error — any error from a strconv call. Mirrors Hare's strconv::error. export type error = !(invalid | overflow); -// base — numeric base for parsing/formatting. Plain i32 (not a named -// enum) because cross-module `strconv.base.DEC` chains miscompile in -// the cstage cgen — it emits a memory load through `base(SB)` rather -// than inlining the enum value. Hare names them as `strconv::base` -// enum values; we expose them as module-level `def`s so callers say -// `strconv.DEC` and the cgen inlines the immediate. +// base — numeric base for parsing/formatting. Mirrors Hare's +// `strconv::base` (Hare uses `enum uint`; we pick `enum i32` since +// the underlying parse/format loops index with i32). // -// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces -// lowercase a-f digits. -export def DEFAULT: i32 = 0; -export def BIN: i32 = 2; -export def OCT: i32 = 8; -export def DEC: i32 = 10; -export def HEX_UPPER: i32 = 16; -export def HEX: i32 = 16; -export def HEX_LOWER: i32 = 17; +// HEX is an alias for HEX_UPPER; HEX_LOWER is a pseudo-base that +// produces lowercase a-f digits. +export type base = enum i32 { + DEFAULT = 0, + BIN = 2, + OCT = 8, + DEC = 10, + HEX_UPPER = 16, + HEX = 16, + HEX_LOWER = 17, +}; -fn basenum(b: i32) i64 = { - if (b == BIN) { return 2; }; - if (b == OCT) { return 8; }; - if (b == HEX) { return 16; }; - if (b == HEX_UPPER) { return 16; }; - if (b == HEX_LOWER) { return 16; }; +fn basenum(b: base) i64 = { + if (b == base.BIN) { return 2; }; + if (b == base.OCT) { return 8; }; + if (b == base.HEX) { return 16; }; + if (b == base.HEX_UPPER) { return 16; }; + if (b == base.HEX_LOWER) { return 16; }; return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: i32) u8 = { +fn basedigit(d: i64, b: base) u8 = { if (d < 10) { return (d + 48): u8; }; let off: i64 = d - 10; - if (b == HEX_LOWER) { return (off + 97): u8; }; + if (b == base.HEX_LOWER) { return (off + 97): u8; }; return (off + 65): u8; }; -// u64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free(r.ptr, r.len: u64). Mirrors Hare's -// strconv::u64tos (Hare returns const str into a static buffer). -export fn u64tos(v: u64, b: i32) str = { +// u64tos — convert v to a base-b numeric string. Returns a view into +// `u64tos_buf` which is overwritten on the next call. Matches Hare's +// strconv::u64tos. +let u64tos_buf: [65]u8; + +export fn u64tos(v: u64, b: base) str = { let nb: u64 = basenum(b): u64; let tmp: [65]u8; let i: i32 = 0; @@ -520,22 +529,25 @@ export fn u64tos(v: u64, b: i32) str = { n = n / nb; i += 1; }; - let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; - buf[out] = tmp[i]; + u64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &u64tos_buf[0]; r.len = out; return r; }; -// i64tos — convert v to a base-b numeric string. Returns owned str; -// release via os.free. Mirrors Hare's strconv::i64tos. -export fn i64tos(v: i64, b: i32) str = { +// i64tos — convert v to a base-b numeric string. Returns a view into +// `i64tos_buf` which is overwritten on the next call. Independent +// buffer from u64tos so i64tos's own call to u64tos doesn't clobber +// the in-flight result. Matches Hare's strconv::i64tos. +let i64tos_buf: [66]u8; + +export fn i64tos(v: i64, b: base) str = { let neg: bool = false; let n: i64 = v; if (n < 0) { neg = true; n = -n; }; @@ -549,37 +561,33 @@ export fn i64tos(v: i64, b: i32) str = { n = n / nb; i += 1; }; - let extra: i32 = 0; - if (neg) { extra = 1; }; - let total: i32 = i + extra; - let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { buf[out] = 45u8; out += 1; }; // '-' + if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; - buf[out] = tmp[i]; + i64tos_buf[out] = tmp[i]; out += 1; }; let r: str; - r.ptr = buf; + r.ptr = &i64tos_buf[0]; r.len = out; return r; }; -export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; -export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; -export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; +export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; -export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; -export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; -export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; +export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; // digval — value of digit byte `c` under base `b`, or -1 if not a // valid digit. Letters are accepted case-insensitively under HEX / // HEX_UPPER; only lowercase under HEX_LOWER. -fn digval(c: u8, b: i32) i32 = { +fn digval(c: u8, b: base) i32 = { if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; - if (b == HEX_LOWER) { + if (b == base.HEX_LOWER) { if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; return -1; }; @@ -592,7 +600,7 @@ fn digval(c: u8, b: i32) i32 = { // No locale, no whitespace, no underscores: optional leading '-' then // digits. Returns invalid with the offending index or overflow on // out-of-range. -export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { +export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; @@ -613,7 +621,7 @@ export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { }; // stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. -export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { +export fn stou64(s: str, b: base) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let nb: u64 = basenum(b): u64; let v: u64 = 0u64; @@ -629,7 +637,7 @@ export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { return v; }; -export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { +export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -643,7 +651,7 @@ export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { return 0: invalid; // unreachable; appeases the path-cov checker }; -export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { +export fn stoi16(s: str, b: base) (i16 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -657,7 +665,7 @@ export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { return 0: invalid; }; -export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { +export fn stoi8(s: str, b: base) (i8 | invalid | overflow) = { let r = stoi64(s, b); match (r) { case let v: i64 => { @@ -671,7 +679,7 @@ export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { return 0: invalid; }; -export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { +export fn stou32(s: str, b: base) (u32 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -684,7 +692,7 @@ export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { return 0: invalid; }; -export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { +export fn stou16(s: str, b: base) (u16 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -697,7 +705,7 @@ export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { return 0: invalid; }; -export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { +export fn stou8(s: str, b: base) (u8 | invalid | overflow) = { let r = stou64(s, b); match (r) { case let v: u64 => { @@ -733,13 +741,14 @@ export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. +let f64tos_buf: [64]u8; + export fn f64tos(v: f64) str = { - let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - tmp[out] = 45u8; // '-' + f64tos_buf[out] = 45u8; // '-' out += 1; f = -f; }; @@ -749,12 +758,9 @@ export fn f64tos(v: f64) str = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; + for (k < s.len) { f64tos_buf[out] = s[k]; out += 1; k += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -773,32 +779,27 @@ export fn f64tos(v: f64) str = { ip += 1; fp = 0; }; - let intstr: str = i64tos(ip, DEC); + let intstr: str = i64tos(ip, base.DEC); let k: i32 = 0; - for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; - os.free(intstr.ptr: *void, intstr.len: u64); + for (k < intstr.len) { f64tos_buf[out] = intstr.ptr[k]; out += 1; k += 1; }; if (fp != 0) { - tmp[out] = 46u8; // '.' + f64tos_buf[out] = 46u8; // '.' out += 1; - let fracstr: str = u64tos(fp: u64, DEC); + let fracstr: str = u64tos(fp: u64, base.DEC); // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → // fp=50000, fracstr="50000", pad one '0' before). let z: i32 = 6 - fracstr.len; - for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + for (z > 0) { f64tos_buf[out] = 48u8; out += 1; z -= 1; }; k = 0; - for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; - os.free(fracstr.ptr: *void, fracstr.len: u64); + for (k < fracstr.len) { f64tos_buf[out] = fracstr.ptr[k]; out += 1; k += 1; }; // Trim trailing zeros in the fractional part. for (out > 0) { - if (tmp[out - 1] != 48u8) { break; }; + if (f64tos_buf[out - 1] != 48u8) { break; }; out -= 1; }; }; - let buf: *u8 = os.alloc(out: u64): *u8; - let q: i32 = 0; - for (q < out) { buf[q] = tmp[q]; q += 1; }; let r: str; - r.ptr = buf; + r.ptr = &f64tos_buf[0]; r.len = out; return r; }; @@ -1083,7 +1084,7 @@ export fn main() i32 = { if (dn != 3) { return 10; }; // Probe 5 — strconv round-trip via the real stdlib. - let s: str = strconv.i64tos(4242i64, strconv.DEC); + let s: str = strconv.i64tos(4242i64, strconv.base.DEC); if (s.len != 4) { return 11; }; if (s.ptr[0] != 52u8) { return 12; }; // '4' if (s.ptr[3] != 50u8) { return 13; }; // '2' diff --git a/selfhost/test/smoke.ww b/selfhost/test/smoke.ww index a05d0587..52880975 100644 --- a/selfhost/test/smoke.ww +++ b/selfhost/test/smoke.ww @@ -130,7 +130,7 @@ export fn main() i32 = { if (dn != 3) { return 10; }; // Probe 5 — strconv round-trip via the real stdlib. - let s: str = strconv.i64tos(4242i64, strconv.DEC); + let s: str = strconv.i64tos(4242i64, strconv.base.DEC); if (s.len != 4) { return 11; }; if (s.ptr[0] != 52u8) { return 12; }; // '4' if (s.ptr[3] != 50u8) { return 13; }; // '2' diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 0453a46e..10f3fe37 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -144,7 +144,7 @@ static const struct row rows[] = { { "use os;\n" "use strconv;\n" "fn main() i32 = {\n" - " let s: str = strconv.i64tos(12345, strconv.DEC);\n" + " let s: str = strconv.i64tos(12345, strconv.base.DEC);\n" " os.write(1, s.ptr, s.len: u64);\n" " os.write(1, \"\\n\".ptr, 1u64);\n" " return s.len;\n" @@ -199,8 +199,8 @@ static const struct row rows[] = { "use strconv;\n" "fn main() i32 = {\n" " fmt.println(\"ww\");\n" - " fmt.println(strconv.i64tos(42, strconv.DEC));\n" - " fmt.println(strconv.i64tos(-7, strconv.DEC));\n" + " fmt.println(strconv.i64tos(42, strconv.base.DEC));\n" + " fmt.println(strconv.i64tos(-7, strconv.base.DEC));\n" " return 0;\n" "};", 0 }, /* struct with i32 fields: MOVL/MOVSXD avoids clobbering neighbors */ @@ -1178,9 +1178,9 @@ static const struct row rows[] = { { "use strconv;\n" "type r_t = (i64 | strconv.invalid | strconv.overflow);\n" "fn main() i32 = {\n" - " let r1: r_t = strconv.stoi64(\"42\", strconv.DEC);\n" - " let r2: r_t = strconv.stoi64(\"-7\", strconv.DEC);\n" - " let r3: r_t = strconv.stoi64(\"abc\", strconv.DEC);\n" + " let r1: r_t = strconv.stoi64(\"42\", strconv.base.DEC);\n" + " let r2: r_t = strconv.stoi64(\"-7\", strconv.base.DEC);\n" + " let r3: r_t = strconv.stoi64(\"abc\", strconv.base.DEC);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: i64 => acc += v: i32;\n" @@ -1204,8 +1204,8 @@ static const struct row rows[] = { { "use strconv;\n" "type r_t = (u64 | strconv.invalid | strconv.overflow);\n" "fn main() i32 = {\n" - " let r1: r_t = strconv.stou64(\"123\", strconv.DEC);\n" - " let r2: r_t = strconv.stou64(\"-1\", strconv.DEC);\n" + " let r1: r_t = strconv.stou64(\"123\", strconv.base.DEC);\n" + " let r2: r_t = strconv.stou64(\"-1\", strconv.base.DEC);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: u64 => acc += v: i32;\n" @@ -1219,7 +1219,7 @@ static const struct row rows[] = { " };\n" " return acc;\n" "};", 123 }, /* 123 + 0 (invalid at index 0 in \"-1\") */ - /* strings.indexbyte and strings.index: now (i32 | void). */ + /* strings.byteindex with (str | rune) needle: returns (i32 | void). */ { "use strings;\n" "fn pick(r: (i32 | void), miss: i32) i32 = {\n" " match (r) {\n" @@ -1230,10 +1230,10 @@ static const struct row rows[] = { "};\n" "fn main() i32 = {\n" " let s: str = \"hello, world\";\n" - " let i1: i32 = pick(strings.indexbyte(s, 44u8), -1);\n" - " let i2: i32 = pick(strings.indexbyte(s, 122u8), -1);\n" - " let i3: i32 = pick(strings.index(s, \"world\"), -1);\n" - " let i4: i32 = pick(strings.index(s, \"nope\"), -1);\n" + " let i1: i32 = pick(strings.byteindex(s, ','), -1);\n" + " let i2: i32 = pick(strings.byteindex(s, 'z'), -1);\n" + " let i3: i32 = pick(strings.byteindex(s, \"world\"), -1);\n" + " let i4: i32 = pick(strings.byteindex(s, \"nope\"), -1);\n" " return i1 + i2 + i3 + i4;\n" "};", 10 }, /* 5 + (-1) + 7 + (-1) */ /* bytes.index: substring search over []u8, (i32 | void). */ @@ -1419,6 +1419,145 @@ static const struct row rows[] = { "// MODULE: main\n" "use pkg;\n" "fn main() i32 = { return pkg.dir.SOUTH as i32; };", 1 }, + /* f64 compound assigns on local: += -= *= /= each modify in place + * (ADDSD/SUBSD/MULSD/DIVSD load-modify-store, not a plain MOVSD that + * would overwrite). 1.5 + 0.5 = 2.0 → 2.0 - 1.0 = 1.0 → 1.0 * 4.0 = + * 4.0 → 4.0 / 2.0 = 2.0 → return 2. */ + { "fn main() i32 = {\n" + " let a: f64 = 1.5;\n" + " a += 0.5;\n" + " a -= 1.0;\n" + " a *= 4.0;\n" + " a /= 2.0;\n" + " return a: i32;\n" + "};", 2 }, + /* f64 compound on a top-level global: LEAQ name(SB), then MOVSD + * load → ADDSD → MOVSD store. 10.0 + 5.0 = 15.0 → 15.0 * 2.0 = + * 30.0 → 30.0 - 20.0 = 10.0 → 10.0 / 5.0 = 2.0. */ + { "let G: f64 = 10.0;\n" + "fn main() i32 = {\n" + " G += 5.0;\n" + " G *= 2.0;\n" + " G -= 20.0;\n" + " G /= 5.0;\n" + " return G: i32;\n" + "};", 2 }, + /* Top-level `[N]u8` array: zero-init DATAW slot + LEAQ name(SB) + * addressing for index and address-of. `buf[i] = c` narrows to + * MOVB; reading back roundtrips through MOVZBQ. */ + { "let buf: [4]u8;\n" + "fn main() i32 = {\n" + " buf[0] = 7u8;\n" + " buf[1] = 35u8;\n" + " return (buf[0] + buf[1]): i32;\n" + "};", 42 }, + /* `&arr[i]` for a top-level array: TK_AMP must compute the + * address, not the value. Then `*p = c` for *u8 stores 1 byte. + * Drives Hare's static-buffer pattern (strconv.*tos). */ + { "let buf: [4]u8;\n" + "fn main() i32 = {\n" + " let p: *u8 = &buf[0];\n" + " *p = 41u8;\n" + " let q: *u8 = &buf[1];\n" + " *q = 1u8;\n" + " return (buf[0] + buf[1]): i32;\n" + "};", 42 }, + /* Cross-module enum member access: `pkg.Enum.MEMBER`. Inner + * N_DOT resolves through SK_USE → SK_TYPE; outer N_DOT folds to + * the member's integer literal. Validates the strconv.base.DEC + * shape that the *tos / sto* signatures now use. */ + { "// MODULE: pkg\n" + "export type base = enum i32 { DEC = 10, HEX = 16 };\n" + "// MODULE: main\n" + "use pkg;\n" + "fn pick(b: pkg.base) i32 = { return b as i32; };\n" + "fn main() i32 = {\n" + " let a: i32 = pick(pkg.base.DEC);\n" + " let b: i32 = pick(pkg.base.HEX);\n" + " return a + b + 16;\n" + "};", 42 }, + /* Sum-typed parameter (str | rune): match-dispatch on a tagged + * union arg widened from a concrete variant at the call site. + * Mirrors lib/strings.byteindex's needle parameter. */ + { "fn pick(n: (str | rune)) i32 = {\n" + " match (n) {\n" + " case let s: str => return s.len + 100;\n" + " case let r: rune => return r: i32;\n" + " };\n" + "};\n" + "fn main() i32 = {\n" + " let a: i32 = pick(\"hi\");\n" + " let b: i32 = pick('?');\n" + " return a + b - 123;\n" + "};", 42 }, /* (2+100) + 63 - 123 = 42 */ + /* Sum-typed (u8 | []u8): 32B slot exceeds the old 24B cap on + * tagged_arg_size. Param fills 4 reg words; the callee must + * read slot+24 (cap) for the slice variant to round-trip. */ + { "use bytes;\n" + "fn main() i32 = {\n" + " let buf: [4]u8;\n" + " buf[0] = 1u8; buf[1] = 2u8; buf[2] = 3u8; buf[3] = 4u8;\n" + " let needle: [2]u8;\n" + " needle[0] = 3u8; needle[1] = 4u8;\n" + " let r1: (i32 | void) = bytes.index(buf[0:4], 3u8);\n" + " let r2: (i32 | void) = bytes.index(buf[0:4], needle[0:2]);\n" + " let a: i32 = 99;\n" + " let b: i32 = 99;\n" + " match (r1) {\n" + " case let i: i32 => a = i;\n" + " case void => a = -1;\n" + " };\n" + " match (r2) {\n" + " case let i: i32 => b = i;\n" + " case void => b = -1;\n" + " };\n" + " return a * 10 + b + 18;\n" /* 2*10 + 2 + 18 = 40, off-by-2 → 42 */ + "};", 40 }, + /* Slice-payload tagged return ([]u8 | E), slot 32B. The 4-reg + * return ABI (AX=tag, DX=ptr, CX=len, R8=cap) lets the callee + * forward all 4 words. Before the bump, slice.len was dropped + * because only 3 regs were used. */ + { "type rterr = !str;\n" + "fn build(n: i32) ([]u8 | rterr) = {\n" + " if (n < 0) { return \"bad\": rterr; };\n" + " let buf: [4]u8;\n" + " buf[0] = 10u8; buf[1] = 20u8; buf[2] = 30u8; buf[3] = 40u8;\n" + " return buf[0:n];\n" + "};\n" + "fn main() i32 = {\n" + " let r: ([]u8 | rterr) = build(3);\n" + " match (r) {\n" + " case let xs: []u8 => {\n" + " if (xs.len != 3) { return 100; };\n" + " return (xs[0] + xs[1] + xs[2]): i32;\n" + " };\n" + " case let e: rterr => return -1;\n" + " };\n" + " return 0;\n" + "};", 60 }, /* 10 + 20 + 30 = 60 */ + /* `[N]TaggedAlias` array: each element is a 24B tagged slot, + * and `arr[i] = literal: TaggedAlias` widens through the + * cgwidentaggedstore path. Validates the cast-peel for + * `expr: TaggedAlias` (which is a widening, not a re-interpret) + * and the alias-resolving element-size lookup. */ + { "type formattable = (i64 | str | bool);\n" + "fn main() i32 = {\n" + " let args: [3]formattable;\n" + " args[0] = 1i64: formattable;\n" + " args[1] = \"hi\": formattable;\n" + " args[2] = true: formattable;\n" + " let s: i32 = 0;\n" + " let i: i32 = 0;\n" + " for (i < args.len) {\n" + " match (args[i]) {\n" + " case let n: i64 => s += n: i32;\n" + " case let v: str => s += v.len;\n" + " case let b: bool => { if (b) { s += 39; }; };\n" + " };\n" + " i += 1;\n" + " };\n" + " return s;\n" + "};", 42 }, /* 1 + 2 + 39 = 42 */ { NULL, 0 } };