ascii: graduate digitval to (i32 | void); selfhost tagged ABI follow-on

ascii.digitval returns (i32 | void) instead of an i32 -1 sentinel.
Two callers updated to match-on the result (lib/ww/lex/lex.ww escape
parse, selfhost/test/smoke.ww probe 6).

`!` would have been more idiomatic at both call sites — both have
verified isxdigit beforehand — but the selfhost parser doesn't yet
recognize postfix `!`/`?`, so using them in bootstrap-bound code
breaks the 993/995 byte-identity gates. Match is fine for now.

Selfhost cgen follow-on for the 8-byte-rounded tagged-union ABI
(landed in 1e2f55a for the C side):
- cgenutil.slotsize: tagged size = 8 (tag) + max(payload), padded to
  8-byte multiple. Was hardcoded 24.
- cgendecl prologue: spill size/8 arg registers, not always 3.
- cgenstmt cglet tagged-call path: spill the CX value-word only when
  the slot is >16 bytes.

All three were emitting 3-register patterns appropriate to (T | str)
sized unions and overflowing the new 16-byte (i32 | void) slots.
This commit is contained in:
2026-05-12 02:10:10 +09:00
parent 4085742853
commit 2f385cec00
9 changed files with 173 additions and 99 deletions

View File

@@ -53,9 +53,9 @@ export fn isxdigit(c: rune) bool = {
return false;
};
// digitval — value of `c` as a hex/decimal digit, or -1 if not one.
// Useful when scanning numeric literals.
export fn digitval(c: rune) i32 = {
// digitval — value of `c` as a hex/decimal digit. void variant means
// `c` isn't a hex digit. Useful when scanning numeric literals.
export fn digitval(c: rune) (i32 | void) = {
if (isdigit(c)) { return (c - 48): i32; };
if (c >= 65) {
if (c <= 70) { return ((c - 65) + 10): i32; };
@@ -63,7 +63,7 @@ export fn digitval(c: rune) i32 = {
if (c >= 97) {
if (c <= 102) { return ((c - 97) + 10): i32; };
};
return -1;
return;
};
// isidstart / isidpart — identifier classes used by the lexer.

View File

@@ -240,8 +240,18 @@ fn escape(l: *lex, out: *i32) bool = {
errat(l, &cp, "bad \\x escape");
return false;
};
let h: i32 = ascii.digitval(hi: rune);
let lv: i32 = ascii.digitval(lo: rune);
let hr: (i32 | void) = ascii.digitval(hi: rune);
let lr: (i32 | void) = ascii.digitval(lo: rune);
let h: i32 = 0;
let lv: i32 = 0;
match (hr) {
case let v: i32 => h = v;
case void => { return false; };
};
match (lr) {
case let v: i32 => lv = v;
case void => { return false; };
};
*out = (h << 4) | lv;
return true;
};

View File

@@ -895,9 +895,9 @@ export fn isxdigit(c: rune) bool = {
return false;
};
// digitval — value of `c` as a hex/decimal digit, or -1 if not one.
// Useful when scanning numeric literals.
export fn digitval(c: rune) i32 = {
// digitval — value of `c` as a hex/decimal digit. void variant means
// `c` isn't a hex digit. Useful when scanning numeric literals.
export fn digitval(c: rune) (i32 | void) = {
if (isdigit(c)) { return (c - 48): i32; };
if (c >= 65) {
if (c <= 70) { return ((c - 65) + 10): i32; };
@@ -905,7 +905,7 @@ export fn digitval(c: rune) i32 = {
if (c >= 97) {
if (c <= 102) { return ((c - 97) + 10): i32; };
};
return -1;
return;
};
// isidstart / isidpart — identifier classes used by the lexer.
@@ -1176,8 +1176,18 @@ fn escape(l: *lex, out: *i32) bool = {
errat(l, &cp, "bad \\x escape");
return false;
};
let h: i32 = ascii.digitval(hi: rune);
let lv: i32 = ascii.digitval(lo: rune);
let hr: (i32 | void) = ascii.digitval(hi: rune);
let lr: (i32 | void) = ascii.digitval(lo: rune);
let h: i32 = 0;
let lv: i32 = 0;
match (hr) {
case let v: i32 => h = v;
case void => { return false; };
};
match (lr) {
case let v: i32 => lv = v;
case void => { return false; };
};
*out = (h << 4) | lv;
return true;
};
@@ -4592,7 +4602,21 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
};
return total;
};
if (k == N_TTAGGED){ return 24; };
if (k == N_TTAGGED){
// Slot = 8 (tag) + max(variant payload sizes), rounded up
// to an 8-byte multiple so the reg-passing ABI (size/8
// words) doesn't drop the last value register. Mirrors C
// cgen's resolve_type for N_TTAGGED.
let v: *node = typn.list;
let maxsz: i32 = 0;
for (v != nil) {
let sz: i32 = slotsize(c, v);
if (sz > maxsz) { maxsz = sz; };
v = v.next;
};
let pad: i32 = (maxsz + 7) & ~7;
return 8 + pad;
};
if (k == N_TNAME) {
let nm: str = typn.str;
if (streq(nm, "str")) { return 16; };
@@ -6400,15 +6424,19 @@ fn cglet(c: *cgen, n: *node) void = {
};
cgexpr(c, rhs);
if (rhsreturnstagged) {
// Spill size/8 registers (tag + value words).
// Slots smaller than 24 don't carry a CX word.
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
if (sz > 16) {
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
};
c.lastwasreturn = 0;
return;
};
@@ -7004,27 +7032,21 @@ fn cgfnparams(c: *cgen, params: *node) void = {
if (p.kind == N_PARAM) {
let nm: str = p.str;
if (istaggedtype(p.lhs)) {
// tagged-union param: passed in 3 regs (tag, v0, v1),
// 24-byte slot.
let off: i32 = localadd(c, nm, 24, p.lhs);
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff(off: i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
// tagged-union param: spill size/8 registers
// (tag + value words). Slot sized to match.
let slot: i32 = slotsize(c, p.lhs);
let off: i32 = localadd(c, nm, slot, p.lhs);
let nw: i32 = slot / 8;
let w: i32 = 0;
for (w < nw) {
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + w*8): i64);
emitline("(BP)\n");
idx += 1;
w += 1;
};
} else { if (isslicetype(c, p.lhs)) {
// slice param: 3 regs (ptr, len, cap), 24-byte slot.
let off: i32 = localadd(c, nm, 24, p.lhs);

View File

@@ -128,27 +128,21 @@ fn cgfnparams(c: *cgen, params: *node) void = {
if (p.kind == N_PARAM) {
let nm: str = p.str;
if (istaggedtype(p.lhs)) {
// tagged-union param: passed in 3 regs (tag, v0, v1),
// 24-byte slot.
let off: i32 = localadd(c, nm, 24, p.lhs);
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff(off: i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
// tagged-union param: spill size/8 registers
// (tag + value words). Slot sized to match.
let slot: i32 = slotsize(c, p.lhs);
let off: i32 = localadd(c, nm, slot, p.lhs);
let nw: i32 = slot / 8;
let w: i32 = 0;
for (w < nw) {
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + w*8): i64);
emitline("(BP)\n");
idx += 1;
w += 1;
};
} else { if (isslicetype(c, p.lhs)) {
// slice param: 3 regs (ptr, len, cap), 24-byte slot.
let off: i32 = localadd(c, nm, 24, p.lhs);

View File

@@ -184,15 +184,19 @@ fn cglet(c: *cgen, n: *node) void = {
};
cgexpr(c, rhs);
if (rhsreturnstagged) {
// Spill size/8 registers (tag + value words).
// Slots smaller than 24 don't carry a CX word.
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
if (sz > 16) {
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
};
c.lastwasreturn = 0;
return;
};

View File

@@ -702,7 +702,21 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
};
return total;
};
if (k == N_TTAGGED){ return 24; };
if (k == N_TTAGGED){
// Slot = 8 (tag) + max(variant payload sizes), rounded up
// to an 8-byte multiple so the reg-passing ABI (size/8
// words) doesn't drop the last value register. Mirrors C
// cgen's resolve_type for N_TTAGGED.
let v: *node = typn.list;
let maxsz: i32 = 0;
for (v != nil) {
let sz: i32 = slotsize(c, v);
if (sz > maxsz) { maxsz = sz; };
v = v.next;
};
let pad: i32 = (maxsz + 7) & ~7;
return 8 + pad;
};
if (k == N_TNAME) {
let nm: str = typn.str;
if (streq(nm, "str")) { return 16; };

View File

@@ -895,9 +895,9 @@ export fn isxdigit(c: rune) bool = {
return false;
};
// digitval — value of `c` as a hex/decimal digit, or -1 if not one.
// Useful when scanning numeric literals.
export fn digitval(c: rune) i32 = {
// digitval — value of `c` as a hex/decimal digit. void variant means
// `c` isn't a hex digit. Useful when scanning numeric literals.
export fn digitval(c: rune) (i32 | void) = {
if (isdigit(c)) { return (c - 48): i32; };
if (c >= 65) {
if (c <= 70) { return ((c - 65) + 10): i32; };
@@ -905,7 +905,7 @@ export fn digitval(c: rune) i32 = {
if (c >= 97) {
if (c <= 102) { return ((c - 97) + 10): i32; };
};
return -1;
return;
};
// isidstart / isidpart — identifier classes used by the lexer.
@@ -1176,8 +1176,18 @@ fn escape(l: *lex, out: *i32) bool = {
errat(l, &cp, "bad \\x escape");
return false;
};
let h: i32 = ascii.digitval(hi: rune);
let lv: i32 = ascii.digitval(lo: rune);
let hr: (i32 | void) = ascii.digitval(hi: rune);
let lr: (i32 | void) = ascii.digitval(lo: rune);
let h: i32 = 0;
let lv: i32 = 0;
match (hr) {
case let v: i32 => h = v;
case void => { return false; };
};
match (lr) {
case let v: i32 => lv = v;
case void => { return false; };
};
*out = (h << 4) | lv;
return true;
};
@@ -4592,7 +4602,21 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
};
return total;
};
if (k == N_TTAGGED){ return 24; };
if (k == N_TTAGGED){
// Slot = 8 (tag) + max(variant payload sizes), rounded up
// to an 8-byte multiple so the reg-passing ABI (size/8
// words) doesn't drop the last value register. Mirrors C
// cgen's resolve_type for N_TTAGGED.
let v: *node = typn.list;
let maxsz: i32 = 0;
for (v != nil) {
let sz: i32 = slotsize(c, v);
if (sz > maxsz) { maxsz = sz; };
v = v.next;
};
let pad: i32 = (maxsz + 7) & ~7;
return 8 + pad;
};
if (k == N_TNAME) {
let nm: str = typn.str;
if (streq(nm, "str")) { return 16; };
@@ -6400,15 +6424,19 @@ fn cglet(c: *cgen, n: *node) void = {
};
cgexpr(c, rhs);
if (rhsreturnstagged) {
// Spill size/8 registers (tag + value words).
// Slots smaller than 24 don't carry a CX word.
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
if (sz > 16) {
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
};
c.lastwasreturn = 0;
return;
};
@@ -7004,27 +7032,21 @@ fn cgfnparams(c: *cgen, params: *node) void = {
if (p.kind == N_PARAM) {
let nm: str = p.str;
if (istaggedtype(p.lhs)) {
// tagged-union param: passed in 3 regs (tag, v0, v1),
// 24-byte slot.
let off: i32 = localadd(c, nm, 24, p.lhs);
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff(off: i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
// tagged-union param: spill size/8 registers
// (tag + value words). Slot sized to match.
let slot: i32 = slotsize(c, p.lhs);
let off: i32 = localadd(c, nm, slot, p.lhs);
let nw: i32 = slot / 8;
let w: i32 = 0;
for (w < nw) {
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + w*8): i64);
emitline("(BP)\n");
idx += 1;
w += 1;
};
} else { if (isslicetype(c, p.lhs)) {
// slice param: 3 regs (ptr, len, cap), 24-byte slot.
let off: i32 = localadd(c, nm, 24, p.lhs);

View File

@@ -376,9 +376,9 @@ export fn isxdigit(c: rune) bool = {
return false;
};
// digitval — value of `c` as a hex/decimal digit, or -1 if not one.
// Useful when scanning numeric literals.
export fn digitval(c: rune) i32 = {
// digitval — value of `c` as a hex/decimal digit. void variant means
// `c` isn't a hex digit. Useful when scanning numeric literals.
export fn digitval(c: rune) (i32 | void) = {
if (isdigit(c)) { return (c - 48): i32; };
if (c >= 65) {
if (c <= 70) { return ((c - 65) + 10): i32; };
@@ -386,7 +386,7 @@ export fn digitval(c: rune) i32 = {
if (c >= 97) {
if (c <= 102) { return ((c - 97) + 10): i32; };
};
return -1;
return;
};
// isidstart / isidpart — identifier classes used by the lexer.
@@ -559,7 +559,11 @@ export fn main() i32 = {
if (!ascii.isalpha(122)) { return 16; }; // 'z'
if (!ascii.isidstart(95)) { return 17; }; // '_'
if (!ascii.isidpart(48)) { return 18; }; // '0' is part
if (ascii.digitval(70) != 15) { return 19; }; // 'F' = 15
let dv: (i32 | void) = ascii.digitval(70);
match (dv) {
case let v: i32 => { if (v != 15) { return 19; }; }; // 'F' = 15
case void => { return 19; };
};
if (ascii.tolower(65) != 97) { return 20; }; // 'A' -> 'a'
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline

View File

@@ -142,7 +142,11 @@ export fn main() i32 = {
if (!ascii.isalpha(122)) { return 16; }; // 'z'
if (!ascii.isidstart(95)) { return 17; }; // '_'
if (!ascii.isidpart(48)) { return 18; }; // '0' is part
if (ascii.digitval(70) != 15) { return 19; }; // 'F' = 15
let dv: (i32 | void) = ascii.digitval(70);
match (dv) {
case let v: i32 => { if (v != 15) { return 19; }; }; // 'F' = 15
case void => { return 19; };
};
if (ascii.tolower(65) != 97) { return 20; }; // 'A' -> 'a'
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline