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