cstage+selfhost+test: fold unary-over-literal in def DATA emit (#24)

Top-level `def NEG: i32 = -100;` skipped DATA emission in both stages
— cstage's emit_defs and wwstage's emitdefconstants each carried a
literal-leaf whitelist that excluded N_UN nodes. Same gap in
check.c's eval_enum_value cstage-side. Surfaced during #10 (lib/os
forced an `at` enum for AT_FDCWD=-100 etc. as workaround).

Factor a single fold_int_literal helper (cstage check.c; wwstage
cgen.ww). Handles N_INTLIT / N_RUNELIT / N_TRUE / N_FALSE / N_NIL
plus N_UN with TK_MINUS / TK_TILDE / TK_PLUS recursively. Consume
from eval_enum_value, emit_defs, emitdefconstants, enumevalmember —
single source of truth for "is this a literal-leaf foldable".

Side effect: cstage's def-emit set widens from {INTLIT, RUNELIT,
TRUE} to match wwstage's pre-existing 5-shape set plus the new
unary peel. Bootstrap byte-id holds (995_self_rebuild green).

Test 631 (def_neg_global): 6 rows × cstage/wwstage run + asm
byte-identity diff. Covers all three unary arms (-, ~, +), positive
regression-pin, i32 + i64 + u32 slots.

Follows up #26: revert lib/os.ww `at` enum to three top-level defs.
This commit is contained in:
2026-05-16 03:00:34 +09:00
parent 2f9d6dc43a
commit cf24af8b26
8 changed files with 457 additions and 48 deletions

View File

@@ -16933,13 +16933,39 @@ fn aliaslookup(c: *cgen, name: str) *node = {
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
// foldintliteral — fold the literal subset usable for top-level
// constant slots: int/rune literal, true/false/nil, and a unary
// +/-/~ over the same (any depth). No sibling-ident, no binary op.
// Shared between enumevalmember (literal leaves) and
// emitdefconstants (top-level def rhs).
//
// Whitelist kept tight on purpose: anything richer (sibling refs,
// arithmetic) belongs in enumevalmember, which calls this for its
// literal leaves and handles the rest itself.
fn foldintliteral(e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: nkind = e.kind;
if (k == nkind.N_INTLIT) { *out = e.uval; return true; };
if (k == nkind.N_RUNELIT) { *out = e.uval; return true; };
if (k == nkind.N_TRUE) { *out = 1u64; return true; };
if (k == nkind.N_FALSE) { *out = 0u64; return true; };
if (k == nkind.N_NIL) { *out = 0u64; return true; };
if (k == nkind.N_UN) {
let v: u64;
if (!foldintliteral(e.lhs, &v)) { return false; };
let op: tkind = e.op;
if (op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == tkind.TK_TILDE) { *out = ~v; return true; };
if (op == tkind.TK_PLUS) { *out = v; return true; };
return false;
};
return false;
};
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
if (foldintliteral(e, out)) { return true; };
let k: nkind = e.kind;
if (k == nkind.N_IDENT) {
let m: *enummember = prev;
for (m != nil) {
@@ -18065,8 +18091,12 @@ fn emitletdataw(c: *cgen, file: *node) void = {
};
};
// emitdefconstants — DATA directive per top-level int-literal `def`.
// 8 bytes little-endian to match what the C cgen emits.
// emitdefconstants — DATA directive per top-level fold-to-literal
// `def`. 8 bytes little-endian to match what the C cgen emits.
// foldintliteral gates: int/rune literal, true/false/nil, and a
// unary +/-/~ over the same. `def NEG: i32 = -100;` arrives as
// N_UN(TK_MINUS, N_INTLIT) — the unary peel is exactly what the
// gate is for.
fn emitdefconstants(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -18075,11 +18105,7 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
let v: u64 = 0u64;
let ok: bool = false;
if (r != nil) {
if (r.kind == nkind.N_INTLIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
ok = foldintliteral(r, &v);
};
if (ok) {
emitline("DATA ");

View File

@@ -117,13 +117,39 @@ fn aliaslookup(c: *cgen, name: str) *node = {
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
// foldintliteral — fold the literal subset usable for top-level
// constant slots: int/rune literal, true/false/nil, and a unary
// +/-/~ over the same (any depth). No sibling-ident, no binary op.
// Shared between enumevalmember (literal leaves) and
// emitdefconstants (top-level def rhs).
//
// Whitelist kept tight on purpose: anything richer (sibling refs,
// arithmetic) belongs in enumevalmember, which calls this for its
// literal leaves and handles the rest itself.
fn foldintliteral(e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: nkind = e.kind;
if (k == nkind.N_INTLIT) { *out = e.uval; return true; };
if (k == nkind.N_RUNELIT) { *out = e.uval; return true; };
if (k == nkind.N_TRUE) { *out = 1u64; return true; };
if (k == nkind.N_FALSE) { *out = 0u64; return true; };
if (k == nkind.N_NIL) { *out = 0u64; return true; };
if (k == nkind.N_UN) {
let v: u64;
if (!foldintliteral(e.lhs, &v)) { return false; };
let op: tkind = e.op;
if (op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == tkind.TK_TILDE) { *out = ~v; return true; };
if (op == tkind.TK_PLUS) { *out = v; return true; };
return false;
};
return false;
};
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
if (foldintliteral(e, out)) { return true; };
let k: nkind = e.kind;
if (k == nkind.N_IDENT) {
let m: *enummember = prev;
for (m != nil) {
@@ -1249,8 +1275,12 @@ fn emitletdataw(c: *cgen, file: *node) void = {
};
};
// emitdefconstants — DATA directive per top-level int-literal `def`.
// 8 bytes little-endian to match what the C cgen emits.
// emitdefconstants — DATA directive per top-level fold-to-literal
// `def`. 8 bytes little-endian to match what the C cgen emits.
// foldintliteral gates: int/rune literal, true/false/nil, and a
// unary +/-/~ over the same. `def NEG: i32 = -100;` arrives as
// N_UN(TK_MINUS, N_INTLIT) — the unary peel is exactly what the
// gate is for.
fn emitdefconstants(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -1259,11 +1289,7 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
let v: u64 = 0u64;
let ok: bool = false;
if (r != nil) {
if (r.kind == nkind.N_INTLIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
ok = foldintliteral(r, &v);
};
if (ok) {
emitline("DATA ");

View File

@@ -16933,13 +16933,39 @@ fn aliaslookup(c: *cgen, name: str) *node = {
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
// foldintliteral — fold the literal subset usable for top-level
// constant slots: int/rune literal, true/false/nil, and a unary
// +/-/~ over the same (any depth). No sibling-ident, no binary op.
// Shared between enumevalmember (literal leaves) and
// emitdefconstants (top-level def rhs).
//
// Whitelist kept tight on purpose: anything richer (sibling refs,
// arithmetic) belongs in enumevalmember, which calls this for its
// literal leaves and handles the rest itself.
fn foldintliteral(e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: nkind = e.kind;
if (k == nkind.N_INTLIT) { *out = e.uval; return true; };
if (k == nkind.N_RUNELIT) { *out = e.uval; return true; };
if (k == nkind.N_TRUE) { *out = 1u64; return true; };
if (k == nkind.N_FALSE) { *out = 0u64; return true; };
if (k == nkind.N_NIL) { *out = 0u64; return true; };
if (k == nkind.N_UN) {
let v: u64;
if (!foldintliteral(e.lhs, &v)) { return false; };
let op: tkind = e.op;
if (op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == tkind.TK_TILDE) { *out = ~v; return true; };
if (op == tkind.TK_PLUS) { *out = v; return true; };
return false;
};
return false;
};
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
if (foldintliteral(e, out)) { return true; };
let k: nkind = e.kind;
if (k == nkind.N_IDENT) {
let m: *enummember = prev;
for (m != nil) {
@@ -18065,8 +18091,12 @@ fn emitletdataw(c: *cgen, file: *node) void = {
};
};
// emitdefconstants — DATA directive per top-level int-literal `def`.
// 8 bytes little-endian to match what the C cgen emits.
// emitdefconstants — DATA directive per top-level fold-to-literal
// `def`. 8 bytes little-endian to match what the C cgen emits.
// foldintliteral gates: int/rune literal, true/false/nil, and a
// unary +/-/~ over the same. `def NEG: i32 = -100;` arrives as
// N_UN(TK_MINUS, N_INTLIT) — the unary peel is exactly what the
// gate is for.
fn emitdefconstants(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -18075,11 +18105,7 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
let v: u64 = 0u64;
let ok: bool = false;
if (r != nil) {
if (r.kind == nkind.N_INTLIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
ok = foldintliteral(r, &v);
};
if (ok) {
emitline("DATA ");