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:
2026-05-13 08:05:01 +09:00
parent 6fd0160c0f
commit 46edb8db4a
23 changed files with 2665 additions and 915 deletions

View File

@@ -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

View File

@@ -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; };

View File

@@ -150,10 +150,24 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
if (bn.len > 0) {
let pat: *node = n.lhs;
if (pat != nil) {
if (isstrtype(c, pat)) { total += 16; }
else { total += 8; };
if (isstrtype(c, pat)) { total += 16; }
else { if (isslicetype(c, pat)) { total += 24; }
else { total += 8; }; };
};
};
// Match arms get a fresh local scope at emission time
// (cgmatch saves c.locals before each arm and restores
// after). scanlocals must mirror that: walk the arm
// body with a saved/restored seenmark set so two arms
// declaring the same name each get their own slot,
// matching the per-arm frame growth the emit phase
// produces.
if (n.body != nil) {
let saved: *local = c.locals;
total += scanlocals(c, n.body);
c.locals = saved;
};
return total;
};
// Tagged-arr/slice index store needs a 24B scratch slot
// (`@tagscr`) for cgwidentaggedstore to materialise the source
@@ -346,10 +360,36 @@ fn cgfnparams(c: *cgen, params: *node) void = {
idx += 1;
w += 1;
};
} else { if (idx < 6 && nw > 1) {
// Partial fit: fill remaining regs, then read
// the tail from positive BP offsets. Mirrors
// the caller's greedy reg fill in pushargsrev.
let off: i32 = localadd(c, nm, slot, p.lhs);
let regs_left: i32 = 6 - idx;
let w: i32 = 0;
for (w < regs_left) {
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + w*8): i64);
emitline("(BP)\n");
idx += 1;
w += 1;
};
for (w < nw) {
emitline("\tMOVQ\t");
emitoff((16 + stkcursor*8): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + w*8): i64);
emitline("(BP)\n");
stkcursor += 1;
w += 1;
};
} else {
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
stkcursor += nw;
};
};};
} else { if (isslicetype(c, p.lhs)) {
if (idx + 3 <= 6) {
let off: i32 = localadd(c, nm, 24, p.lhs);
@@ -430,7 +470,12 @@ fn cgfn(c: *cgen, fn_: *node) void = {
};
a = a.next;
};
if (!isffi) {
// `main` is the linker entry-point convention; even
// when not marked `export`, it must keep its bare
// name so w6l's _start can resolve `CALL main(SB)`.
// Mirror of cmd/w6c/cgen.c collectmods exemption.
let isentry: bool = streq(fn_.str, "main");
if (!isffi && !isentry) {
os.write(1, fn_.module.ptr, fn_.module.len: u64);
os.write(1, ".".ptr, 1u64);
};

View File

@@ -534,7 +534,7 @@ fn cgindex(c: *cgen, n: *node) void = {
let bn: str = base.str;
baselocal = localfindnode(c, bn);
if (baselocal != nil) {
esz = elemsizeof(baselocal.tnode);
esz = elemsizeofc(c, baselocal.tnode);
signed_elem = elemissigned(baselocal.tnode);
} else {
let tn: *node = letvartnode(c, bn);
@@ -542,13 +542,13 @@ fn cgindex(c: *cgen, n: *node) void = {
if (tn.kind == nkind.N_TARRAY) {
isglobalarr = true;
globalname = bn;
esz = elemsizeof(tn);
esz = elemsizeofc(c, tn);
signed_elem = elemissigned(tn);
};
if (tn.kind == nkind.N_TPTR) {
isglobalptr = true;
globalname = bn;
esz = elemsizeof(tn);
esz = elemsizeofc(c, tn);
signed_elem = elemissigned(tn);
};
};
@@ -613,6 +613,9 @@ fn cgindex(c: *cgen, n: *node) void = {
};
emitline("\tADDQ\tAX, BX\n");
if (elem_tagged) {
if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n");
};
if (elem_slot_sz > 16) {
emitline("\tMOVQ\t16(BX), CX\n");
};
@@ -651,6 +654,9 @@ fn cgindex(c: *cgen, n: *node) void = {
};
emitline("\tADDQ\tAX, BX\n");
if (elem_tagged) {
if (elem_slot_sz > 24) {
emitline("\tMOVQ\t24(BX), R8\n");
};
if (elem_slot_sz > 16) {
emitline("\tMOVQ\t16(BX), CX\n");
};
@@ -809,7 +815,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
};
} else {
// Non-ident scrutinee (call result, arr[i], ?, etc.).
// Spill into a 24B `@match_spill` scratch slot and
// Spill into an `@match_spill` scratch slot and
// dispatch off it. Tagged returns (N_CALL) follow the
// AX:DX:CX convention; tagged-element loads (N_INDEX)
// after the cgindex fix produce the same triple.
@@ -864,6 +870,16 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tCX, ");
emitoff((scrutoff + 16): i64);
emitline("(BP)\n");
// R8 carries the 4th return word when the
// scrutinee's tagged union has a slice-payload
// variant (slot 32B). Harmless for narrower
// returns — R8 is callee-clobbered either way.
let ssz: i32 = slotsize(c, scrutt);
if (ssz > 24) {
emitline("\tMOVQ\tR8, ");
emitoff((scrutoff + 24): i64);
emitline("(BP)\n");
};
};
};
};
@@ -949,25 +965,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
};
} else {
let bsz: i32 = 8;
if (isstrtype(c, pat)) { bsz = 16; };
if (isstrtype(c, pat)) { bsz = 16; }
else { if (isslicetype(c, pat)) { bsz = 24; }; };
// localalloc (not localadd): match-arm
// binds don't dedup with same-named binds
// in *other* matches, since C's cgexpr
// allocates a fresh slot per match expr.
let voff: i32 = localalloc(c, bn, bsz, pat);
emitline("\tMOVQ\t");
emitoff((scrutoff + 8): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff(voff: i64);
emitline("(BP)\n");
if (bsz == 16) {
let bw: i32 = 0;
for (bw < bsz) {
emitline("\tMOVQ\t");
emitoff((scrutoff + 16): i64);
emitoff((scrutoff + 8 + bw): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((voff + 8): i64);
emitoff((voff + bw): i64);
emitline("(BP)\n");
bw += 8;
};
};
};
@@ -1589,6 +1602,20 @@ fn cgdot(c: *cgen, n: *node) void = {
};};
};
};
// Nested module-qualified field where the chain didn't fold to a
// known shape (raw w6c on a single file with `use mod;` but no
// driver concatenation — the inner enum / struct hasn't been
// seen). Emit `MOVQ <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,16 +3166,55 @@ fn cgassign(c: *cgen, n: *node) void = {
lvf = lvf.lvnext;
};
};
if (isfg && n.op == tkind.TK_ASSIGN) {
if (isfg) {
cgexpr(c, n.rhs);
let mov: str = "MOVSD";
if (isf32g) { mov = "MOVSS"; };
let addf: str = "ADDSD";
let subf: str = "SUBSD";
let mulf: str = "MULSD";
let divf: str = "DIVSD";
if (isf32g) {
mov = "MOVSS";
addf = "ADDSS";
subf = "SUBSS";
mulf = "MULSS";
divf = "DIVSS";
};
emitline("\tLEAQ\t");
emitsymname(c, nm);
emitline("(SB), CX\n");
if (n.op == tkind.TK_ASSIGN) {
emitline("\t");
emitline(mov);
emitline("\tX0, (CX)\n");
return;
};
// Compound: X1 = load; X1 OP= X0; store X1.
// ADDSD/SUBSD/MULSD/DIVSD are register-register
// only, so we can't combine direct to memory.
let fop: str;
fop.ptr = nil; fop.len = 0;
if (n.op == tkind.TK_PLUSEQ) { fop = addf; };
if (n.op == tkind.TK_MINUSEQ) { fop = subf; };
if (n.op == tkind.TK_STAREQ) { fop = mulf; };
if (n.op == tkind.TK_SLASHEQ) { fop = divf; };
if (fop.len == 0) {
// Unsupported (e.g., %= on float):
// fall back to plain store of rhs.
emitline("\t");
emitline(mov);
emitline("\tX0, (CX)\n");
return;
};
emitline("\t");
emitline(mov);
emitline("\tX0, (CX)\n");
emitline("\t(CX), X1\n");
emitline("\t");
emitline(fop);
emitline("\tX0, X1\n");
emitline("\t");
emitline(mov);
emitline("\tX1, (CX)\n");
return;
};
cgexpr(c, n.rhs);
@@ -3151,15 +3293,56 @@ fn cgassign(c: *cgen, n: *node) void = {
lcf32 = isf32type(c, lcn.tnode);
};
// Float-typed local: rhs lands in X0; store via MOVSD/
// MOVSS, no AX shuffle. Only plain `=` is wired; compound
// float-assign isn't.
if (lcf && n.op == tkind.TK_ASSIGN) {
// MOVSS, no AX shuffle. Compound (+= -= *= /=) loads
// slot into X1, combines into X1, stores X1 back —
// ADDSD/SUBSD/MULSD/DIVSD are register-register only.
if (lcf) {
cgexpr(c, n.rhs);
let mov: str = "MOVSD";
if (lcf32) { mov = "MOVSS"; };
let addf: str = "ADDSD";
let subf: str = "SUBSD";
let mulf: str = "MULSD";
let divf: str = "DIVSD";
if (lcf32) {
mov = "MOVSS";
addf = "ADDSS";
subf = "SUBSS";
mulf = "MULSS";
divf = "DIVSS";
};
if (n.op == tkind.TK_ASSIGN) {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff(off: i64);
emitline("(BP)\n");
return;
};
let fop: str;
fop.ptr = nil; fop.len = 0;
if (n.op == tkind.TK_PLUSEQ) { fop = addf; };
if (n.op == tkind.TK_MINUSEQ) { fop = subf; };
if (n.op == tkind.TK_STAREQ) { fop = mulf; };
if (n.op == tkind.TK_SLASHEQ) { fop = divf; };
if (fop.len == 0) {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff(off: i64);
emitline("(BP)\n");
return;
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitline("\t");
emitoff(off: i64);
emitline("(BP), X1\n");
emitline("\t");
emitline(fop);
emitline("\tX0, X1\n");
emitline("\t");
emitline(mov);
emitline("\tX1, ");
emitoff(off: i64);
emitline("(BP)\n");
return;

View File

@@ -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);

View File

@@ -101,7 +101,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
return rest + widensz / 8;
};
cgexpr(c, arg);
if (nodeisstr(c, arg)) {
if (nodeisslice(c, arg)) {
// Slice payload (24B): cgexpr leaves (AX=ptr, BX=len,
// CX=cap). Slot layout: [+0]=tag, [+8]=ptr, [+16]=len,
// [+24]=cap. Push high→low so pop drains tag first.
// Requires widensz >= 32; a smaller slot would mean the
// destination union doesn't list slice as a variant
// (caller should have flagged a type error).
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tAX\n");
emitline("\tMOVQ\t$");
emitint(widentag: i64);
emitline(", AX\n");
emitline("\tPUSHQ\tAX\n");
} else { if (nodeisstr(c, arg)) {
// slot 24: [+0]=tag,[+8]=ptr,[+16]=len. Push high→low
// so pop drains tag first into arg-reg[0].
emitline("\tPUSHQ\tBX\n");
@@ -114,16 +128,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
// Scalar variant: single value word at +8. Pad a zero
// high word when slot is 24B (some other variant of
// the union is 16B-shaped).
if (widensz > 16) {
let pp: i32 = widensz - 8;
for (pp > 8) {
emitline("\tXORQ\tDX, DX\n");
emitline("\tPUSHQ\tDX\n");
pp -= 8;
};
emitline("\tPUSHQ\tAX\n");
emitline("\tMOVQ\t$");
emitint(widentag: i64);
emitline(", AX\n");
emitline("\tPUSHQ\tAX\n");
};
};};
return rest + widensz / 8;
};
// nkind.N_SLICE expression as arg: `buf[lo:hi]` builds a slice header
@@ -209,25 +225,28 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
// Slice/tagged ident args: emit per-register MOVQ+PUSHQ pairs in
// reverse order (cap/v1, len/v0, ptr/tag) so a left-to-right pop
// into argregs lands the canonical (ptr/tag, len/v0, cap/v1).
// For tagged ident with a >24B slot (slice-payload variant),
// push a fourth word from off+24.
if (arg.kind == nkind.N_IDENT) {
let nm: str = arg.str;
let lc: *local = localfindnode(c, nm);
if (lc != nil) {
let off: i32 = lc.off;
if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) {
emitline("\tMOVQ\t");
emitoff((off + 16): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
emitline("\tMOVQ\t");
emitoff((off + 8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
emitline("\tMOVQ\t");
emitoff(off: i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
return rest + 3;
let nwords: i32 = 3;
if (istaggedtype(c, lc.tnode)) {
let ssz: i32 = slotsize(c, lc.tnode);
nwords = ssz / 8;
};
let w: i32 = nwords - 1;
for (w >= 0) {
emitline("\tMOVQ\t");
emitoff((off + w*8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
w -= 1;
};
return rest + nwords;
};
};
};
@@ -610,6 +629,9 @@ fn dotinnerstructptr(c: *cgen, n: *node) *node = {
// elemsizeof — given the type node of an indexable (`*T`, `[]T`,
// `[N]T`, `str`), return the byte size of one element (1 for u8/i8/
// bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback).
// For aliased element types (e.g. `[N]formattable`), callers that
// need the resolved slot size should use elemsizeofc(c, t) which
// follows aliases via slotsize.
fn elemsizeof(t: *node) i32 = {
if (t == nil) { return 1; };
let k: nkind = t.kind;
@@ -636,6 +658,27 @@ fn elemsizeof(t: *node) i32 = {
return 8;
};
// elemsizeofc — like elemsizeof but resolves aliased element types
// (struct / tagged / `type foo = bar;`) via slotsize. Used where
// cgindex / cgassign need a correct stride for `[N]Alias` arrays
// whose Alias resolves to a tagged union (e.g. `[N]formattable`).
fn elemsizeofc(c: *cgen, t: *node) i32 = {
if (t == nil) { return 1; };
let direct: i32 = elemsizeof(t);
if (direct != 8) { return direct; };
let k: nkind = t.kind;
let elem: *node = nil;
if (k == nkind.N_TPTR) { elem = t.lhs; };
if (k == nkind.N_TSLICE) { elem = t.lhs; };
if (k == nkind.N_TARRAY) { elem = t.lhs; };
if (elem == nil) { return direct; };
if (elem.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elem.str);
if (ps > 0) { return ps; };
};
return slotsize(c, elem);
};
// nodeisunsigned — best-effort cgen-time inference from the AST. We
// don't have a typed AST yet, so we walk surface nodes:
// nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet)
@@ -986,9 +1029,20 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (ps > 0) { esz = ps; }
else {
// Named struct / aliased type: size off
// the structinfo if present.
// the structinfo if present, else follow
// the alias via aliaslookup so
// `[N]formattable` reads the resolved
// tagged slot (e.g. 24B for
// `(i64|str|bool)`), not the fall-
// through 8B.
let si: *structinfo = structlookup(c, en);
if (si != nil) { esz = si.totsize; };
if (si != nil) { esz = si.totsize; }
else { if (c != nil) {
let al: *node = aliaslookup(c, en);
if (al != nil) {
esz = slotsize(c, al);
};
}; };
};
} else { if (elemn.kind == nkind.N_TTAGGED) {
// Tagged-union element: full slot (8 tag +
@@ -1734,6 +1788,53 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
emitline("(BP)\n");
return;
};
// `expr: TaggedAlias` where the cast's destination IS the union
// itself is a widening, not a re-interpret. cgexpr on a CAST
// produces the inner's register shape (str: AX=ptr, BX=len), not
// the tagged AX/DX/CX triple — so peel to the inner and route
// through the matching concrete-variant branch below. A cast to
// a concrete variant (`7: i32`) is left intact so the existing
// scalar / str / slice branches pick the right variant tag.
if (src != nil) {
if (src.kind == nkind.N_CAST) {
if (src.lhs != nil) {
let inner: *node = src.lhs;
let inneristagged: bool = false;
if (inner.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, inner.str);
if (lc != nil) {
inneristagged = istaggedtype(c, lc.tnode);
};
};
if (rhstaggedabicall(c, inner)) {
inneristagged = true;
};
// Cast's destination = the dst tagged union
// itself? The rhs of N_CAST holds the target
// type. Compare nominally via str match on
// the tagged-alias name.
let castisdst: bool = false;
let castrhs: *node = src.rhs;
if (castrhs != nil) {
if (castrhs.kind == nkind.N_TTAGGED) {
castisdst = true;
};
if (castrhs.kind == nkind.N_TNAME) {
if (dst != nil) {
if (dst.kind == nkind.N_TNAME) {
if (streq(castrhs.str, dst.str)) {
castisdst = true;
};
};
};
};
};
if (castisdst && !inneristagged) {
src = inner;
};
};
};
};
// Tagged source ident: byte-copy slot words then tag-remap.
let st: *node = rhstaggedident(c, src);
if (st != nil) {
@@ -1763,8 +1864,9 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
cgwidentagremap(c, dt, st, slot_off);
return;
};
// Tagged source via AX/DX/CX register ABI (N_CALL, N_INDEX of
// tagged element).
// Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX
// of tagged element). R8 carries the 4th word for slice-payload
// variants (slot 32B).
if (rhstaggedabicall(c, src)) {
cgexpr(c, src);
emitline("\tMOVQ\tAX, ");
@@ -1780,6 +1882,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
emitoff((slot_off + 16): i64);
emitline("(BP)\n");
};
if (slot_sz > 24) {
emitline("\tMOVQ\tR8, ");
emitoff((slot_off + 24): i64);
emitline("(BP)\n");
};
return;
};
// Struct payload (literal or ident).
@@ -1898,6 +2005,28 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz:
emitline("(BP)\n");
return;
};
// Slice payload (24B): cgexpr leaves (AX=ptr, BX=len, CX=cap).
// Slot layout: [+0]=tag, [+8]=ptr, [+16]=len, [+24]=cap.
if (nodeisslice(c, src)) {
cgexpr(c, src);
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot_off + 16): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot_off + 24): i64);
emitline("(BP)\n");
let tag: i32 = taggedvariantindex(c, dt, src);
if (tag < 0) { tag = 0; };
emitline("\tMOVQ\t$");
emitint(tag: i64);
emitline(", ");
emitoff(slot_off: i64);
emitline("(BP)\n");
return;
};
// Scalar payload.
cgexpr(c, src);
emitline("\tMOVQ\tAX, ");

File diff suppressed because it is too large Load Diff

View File

@@ -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; };

View File

@@ -269,53 +269,46 @@ export fn hassuffix(s: str, suf: str) bool = {
return true;
};
// indexbyte — first byte position of byte `c` in `s`. Mirrors
// Hare's strings::byteindex when the needle is a single ASCII rune,
// renamed to match bytes.indexbyte and to disambiguate from Hare's
// `byteindex(haystack, needle: (str | rune))` which we don't have
// the union-arg ABI for yet.
export fn indexbyte(s: str, c: u8) (i32 | void) = {
let i: i32 = 0;
for (i < s.len) {
if (s[i] == c) { return i; };
i += 1;
};
return;
};
// rindexbyte — last byte position of byte `c` in `s`.
export fn rindexbyte(s: str, c: u8) (i32 | void) = {
let i: i32 = s.len - 1;
for (i >= 0) {
if (s[i] == c) { return i; };
i -= 1;
};
return;
};
// index — first index of `sub` in `s`. Naive scan; fine for short
// patterns and small strings, which dominate config and CLI parsing.
// Empty `sub` matches at 0.
export fn index(s: str, sub: str) (i32 | void) = {
if (sub.len == 0) { return 0; };
if (sub.len > s.len) { return; };
let last: i32 = s.len - sub.len;
let i: i32 = 0;
for (i <= last) {
let j: i32 = 0;
let ok: bool = true;
for (j < sub.len) {
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
else { j += 1; };
// byteindex — first byte position of `needle` in `s`. Mirrors Hare's
// strings::byteindex: a single-codepoint rune scans for the byte that
// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode),
// a str needle scans for the substring. Returns void if absent.
export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = {
match (needle) {
case let r: rune => {
let c: u8 = r: u8;
let i: i32 = 0;
for (i < s.len) {
if (s[i] == c) { return i; };
i += 1;
};
if (ok) { return i; };
i += 1;
return;
};
case let sub: str => {
if (sub.len == 0) { return 0; };
if (sub.len > s.len) { return; };
let last: i32 = s.len - sub.len;
let i: i32 = 0;
for (i <= last) {
let j: i32 = 0;
let ok: bool = true;
for (j < sub.len) {
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
else { j += 1; };
};
if (ok) { return i; };
i += 1;
};
return;
};
};
return;
};
// contains — true iff `sub` appears in `s`. Mirrors Hare's
// strings::contains shape (byte-wise on the str-needle case).
export fn contains(s: str, sub: str) bool = {
let r: (i32 | void) = index(s, sub);
let r: (i32 | void) = byteindex(s, sub);
match (r) {
case let i: i32 => return true;
case void => return false;
@@ -359,21 +352,37 @@ export fn dup(s: str) str = {
return r;
};
// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex
// (slice case). Empty `sub` matches at s.len.
export fn rindex(s: str, sub: str) (i32 | void) = {
if (sub.len == 0) { return s.len; };
if (sub.len > s.len) { return; };
let i: i32 = s.len - sub.len;
for (i >= 0) {
let j: i32 = 0;
let ok: bool = true;
for (j < sub.len) {
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
else { j += 1; };
// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's
// strings::rbyteindex. Rune needle scans for the byte that encodes it
// (ASCII only); str needle scans for the substring. Empty str needle
// matches at s.len.
export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = {
match (needle) {
case let r: rune => {
let c: u8 = r: u8;
let i: i32 = s.len - 1;
for (i >= 0) {
if (s[i] == c) { return i; };
i -= 1;
};
if (ok) { return i; };
i -= 1;
return;
};
case let sub: str => {
if (sub.len == 0) { return s.len; };
if (sub.len > s.len) { return; };
let i: i32 = s.len - sub.len;
for (i >= 0) {
let j: i32 = 0;
let ok: bool = true;
for (j < sub.len) {
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
else { j += 1; };
};
if (ok) { return i; };
i -= 1;
};
return;
};
};
return;
};
@@ -450,12 +459,11 @@ export fn trimbyte(s: str, c: u8) str = {
// MODULE: strconv
// strconv — number↔string conversions.
//
// Mirrors Hare's strconv:: surface. The *tos functions return a fresh
// owned `str`; release via os.free(r.ptr, r.len: u64) when done.
// Hare returns `const str` into a static buffer; ww allocates per
// call because the wwstage cgen doesn't currently support mutating a
// module-level `*u8` (so a lazy-init shared buffer isn't expressible
// today). Graduate to the static-buffer shape once that lands.
// Mirrors Hare's strconv:: surface. The *tos functions return a
// `const str` view into a module-level buffer that is overwritten on
// the next call to the same function; callers must copy the bytes if
// they need to outlive the next invocation. See [[strings.dup]] to
// duplicate. Matches Hare's strconv::*tos semantics.
use os;
use strings;
@@ -472,43 +480,44 @@ export type overflow = !void;
// error — any error from a strconv call. Mirrors Hare's strconv::error.
export type error = !(invalid | overflow);
// base — numeric base for parsing/formatting. Plain i32 (not a named
// enum) because cross-module `strconv.base.DEC` chains miscompile in
// the cstage cgen — it emits a memory load through `base(SB)` rather
// than inlining the enum value. Hare names them as `strconv::base`
// enum values; we expose them as module-level `def`s so callers say
// `strconv.DEC` and the cgen inlines the immediate.
// base — numeric base for parsing/formatting. Mirrors Hare's
// `strconv::base` (Hare uses `enum uint`; we pick `enum i32` since
// the underlying parse/format loops index with i32).
//
// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces
// lowercase a-f digits.
export def DEFAULT: i32 = 0;
export def BIN: i32 = 2;
export def OCT: i32 = 8;
export def DEC: i32 = 10;
export def HEX_UPPER: i32 = 16;
export def HEX: i32 = 16;
export def HEX_LOWER: i32 = 17;
// HEX is an alias for HEX_UPPER; HEX_LOWER is a pseudo-base that
// produces lowercase a-f digits.
export type base = enum i32 {
DEFAULT = 0,
BIN = 2,
OCT = 8,
DEC = 10,
HEX_UPPER = 16,
HEX = 16,
HEX_LOWER = 17,
};
fn basenum(b: i32) i64 = {
if (b == BIN) { return 2; };
if (b == OCT) { return 8; };
if (b == HEX) { return 16; };
if (b == HEX_UPPER) { return 16; };
if (b == HEX_LOWER) { return 16; };
fn basenum(b: base) i64 = {
if (b == base.BIN) { return 2; };
if (b == base.OCT) { return 8; };
if (b == base.HEX) { return 16; };
if (b == base.HEX_UPPER) { return 16; };
if (b == base.HEX_LOWER) { return 16; };
return 10; // DEC and DEFAULT
};
fn basedigit(d: i64, b: i32) u8 = {
fn basedigit(d: i64, b: base) u8 = {
if (d < 10) { return (d + 48): u8; };
let off: i64 = d - 10;
if (b == HEX_LOWER) { return (off + 97): u8; };
if (b == base.HEX_LOWER) { return (off + 97): u8; };
return (off + 65): u8;
};
// u64tos — convert v to a base-b numeric string. Returns owned str;
// release via os.free(r.ptr, r.len: u64). Mirrors Hare's
// strconv::u64tos (Hare returns const str into a static buffer).
export fn u64tos(v: u64, b: i32) str = {
// u64tos — convert v to a base-b numeric string. Returns a view into
// `u64tos_buf` which is overwritten on the next call. Matches Hare's
// strconv::u64tos.
let u64tos_buf: [65]u8;
export fn u64tos(v: u64, b: base) str = {
let nb: u64 = basenum(b): u64;
let tmp: [65]u8;
let i: i32 = 0;
@@ -520,22 +529,25 @@ export fn u64tos(v: u64, b: i32) str = {
n = n / nb;
i += 1;
};
let buf: *u8 = os.alloc(i: u64): *u8;
let out: i32 = 0;
for (i > 0) {
i -= 1;
buf[out] = tmp[i];
u64tos_buf[out] = tmp[i];
out += 1;
};
let r: str;
r.ptr = buf;
r.ptr = &u64tos_buf[0];
r.len = out;
return r;
};
// i64tos — convert v to a base-b numeric string. Returns owned str;
// release via os.free. Mirrors Hare's strconv::i64tos.
export fn i64tos(v: i64, b: i32) str = {
// i64tos — convert v to a base-b numeric string. Returns a view into
// `i64tos_buf` which is overwritten on the next call. Independent
// buffer from u64tos so i64tos's own call to u64tos doesn't clobber
// the in-flight result. Matches Hare's strconv::i64tos.
let i64tos_buf: [66]u8;
export fn i64tos(v: i64, b: base) str = {
let neg: bool = false;
let n: i64 = v;
if (n < 0) { neg = true; n = -n; };
@@ -549,37 +561,33 @@ export fn i64tos(v: i64, b: i32) str = {
n = n / nb;
i += 1;
};
let extra: i32 = 0;
if (neg) { extra = 1; };
let total: i32 = i + extra;
let buf: *u8 = os.alloc(total: u64): *u8;
let out: i32 = 0;
if (neg) { buf[out] = 45u8; out += 1; }; // '-'
if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-'
for (i > 0) {
i -= 1;
buf[out] = tmp[i];
i64tos_buf[out] = tmp[i];
out += 1;
};
let r: str;
r.ptr = buf;
r.ptr = &i64tos_buf[0];
r.len = out;
return r;
};
export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); };
export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); };
export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); };
export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); };
export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); };
export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); };
export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); };
export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); };
export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); };
export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); };
export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); };
export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); };
// digval — value of digit byte `c` under base `b`, or -1 if not a
// valid digit. Letters are accepted case-insensitively under HEX /
// HEX_UPPER; only lowercase under HEX_LOWER.
fn digval(c: u8, b: i32) i32 = {
fn digval(c: u8, b: base) i32 = {
if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; };
if (b == HEX_LOWER) {
if (b == base.HEX_LOWER) {
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
return -1;
};
@@ -592,7 +600,7 @@ fn digval(c: u8, b: i32) i32 = {
// No locale, no whitespace, no underscores: optional leading '-' then
// digits. Returns invalid with the offending index or overflow on
// out-of-range.
export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = {
export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = {
if (s.len == 0) { return 0: invalid; };
let i: i32 = 0;
let neg: bool = false;
@@ -613,7 +621,7 @@ export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = {
};
// stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64.
export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = {
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
if (s.len == 0) { return 0: invalid; };
let nb: u64 = basenum(b): u64;
let v: u64 = 0u64;
@@ -629,7 +637,7 @@ export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = {
return v;
};
export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = {
export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => {
@@ -643,7 +651,7 @@ export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = {
return 0: invalid; // unreachable; appeases the path-cov checker
};
export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = {
export fn stoi16(s: str, b: base) (i16 | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => {
@@ -657,7 +665,7 @@ export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = {
return 0: invalid;
};
export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = {
export fn stoi8(s: str, b: base) (i8 | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => {
@@ -671,7 +679,7 @@ export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = {
return 0: invalid;
};
export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = {
export fn stou32(s: str, b: base) (u32 | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => {
@@ -684,7 +692,7 @@ export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = {
return 0: invalid;
};
export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = {
export fn stou16(s: str, b: base) (u16 | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => {
@@ -697,7 +705,7 @@ export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = {
return 0: invalid;
};
export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = {
export fn stou8(s: str, b: base) (u8 | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => {
@@ -733,13 +741,14 @@ export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = {
// the ww-side wwdump currently skips TK_FLOAT.fval while the C side
// %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses:
// build f64 constants via int-to-f64 casts.
let f64tos_buf: [64]u8;
export fn f64tos(v: f64) str = {
let tmp: [64]u8;
let out: i32 = 0;
let f: f64 = v;
let zero: f64 = 0: f64;
if (f < zero) {
tmp[out] = 45u8; // '-'
f64tos_buf[out] = 45u8; // '-'
out += 1;
f = -f;
};
@@ -749,12 +758,9 @@ export fn f64tos(v: f64) str = {
if (f >= cap) {
let s: str = "huge";
let k: i32 = 0;
for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; };
let buf: *u8 = os.alloc(out: u64): *u8;
let q: i32 = 0;
for (q < out) { buf[q] = tmp[q]; q += 1; };
for (k < s.len) { f64tos_buf[out] = s[k]; out += 1; k += 1; };
let r: str;
r.ptr = buf;
r.ptr = &f64tos_buf[0];
r.len = out;
return r;
};
@@ -773,32 +779,27 @@ export fn f64tos(v: f64) str = {
ip += 1;
fp = 0;
};
let intstr: str = i64tos(ip, DEC);
let intstr: str = i64tos(ip, base.DEC);
let k: i32 = 0;
for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; };
os.free(intstr.ptr: *void, intstr.len: u64);
for (k < intstr.len) { f64tos_buf[out] = intstr.ptr[k]; out += 1; k += 1; };
if (fp != 0) {
tmp[out] = 46u8; // '.'
f64tos_buf[out] = 46u8; // '.'
out += 1;
let fracstr: str = u64tos(fp: u64, DEC);
let fracstr: str = u64tos(fp: u64, base.DEC);
// Pad fractional to 6 digits with leading zeros (e.g. 0.05 →
// fp=50000, fracstr="50000", pad one '0' before).
let z: i32 = 6 - fracstr.len;
for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; };
for (z > 0) { f64tos_buf[out] = 48u8; out += 1; z -= 1; };
k = 0;
for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; };
os.free(fracstr.ptr: *void, fracstr.len: u64);
for (k < fracstr.len) { f64tos_buf[out] = fracstr.ptr[k]; out += 1; k += 1; };
// Trim trailing zeros in the fractional part.
for (out > 0) {
if (tmp[out - 1] != 48u8) { break; };
if (f64tos_buf[out - 1] != 48u8) { break; };
out -= 1;
};
};
let buf: *u8 = os.alloc(out: u64): *u8;
let q: i32 = 0;
for (q < out) { buf[q] = tmp[q]; q += 1; };
let r: str;
r.ptr = buf;
r.ptr = &f64tos_buf[0];
r.len = out;
return r;
};
@@ -1083,7 +1084,7 @@ export fn main() i32 = {
if (dn != 3) { return 10; };
// Probe 5 — strconv round-trip via the real stdlib.
let s: str = strconv.i64tos(4242i64, strconv.DEC);
let s: str = strconv.i64tos(4242i64, strconv.base.DEC);
if (s.len != 4) { return 11; };
if (s.ptr[0] != 52u8) { return 12; }; // '4'
if (s.ptr[3] != 50u8) { return 13; }; // '2'

View File

@@ -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'