check: def rhs const-fold resolves sibling/imported defs + casts (#88)
ww top-level def rhs const-fold was literal-only (fold_int_literal at the codegen emit-defs step), so a def referencing another def, an imported def, or a cast was inexpressible -- blocking faithful types/types::c/math/strconv ports whose defs cross-reference. Fold at CHECK time: a recursive eval_def_const (pass-2 N_DEF arm, both stages) resolves N_IDENT/N_DOT via the checker's existing scope lookup to the target def's rhs, evaluates N_BIN through a shared fold_binop core (factored out of eval_enum_value so both compile-time-int-eval paths share one wrap/shift/divide table), strips identity/widening casts, and stamps rhs -> N_INTLIT. cgen is UNTOUCHED -- its existing literal-emit lays the DATA row. Gated to fire only when the plain literal fold fails, so existing defs keep their node and emitted asm is byte-identical (990-997 unperturbed by construction). Guards (rule 7): recursion depth cap fails loud on a def cycle (same/cross-module); a narrowing cast (rhs outside target range) fails loud rather than silently truncating. Both stages' eval_def_const stamp identically (shared fold_binop semantics) so the substituted literal -- and byte-id -- holds across stages (rule 10, at the check pass). a1 (same-module) + a2 (cross-module imported def) land together: the driver concatenates imports into one flat scope. Coverage: test/wcc/732_def_const_fold.
This commit is contained in:
7
Makefile
7
Makefile
@@ -217,6 +217,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_arch \
|
||||
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
||||
$(BIN)/test_at_test $(BIN)/test_let_global $(BIN)/test_def_neg_global \
|
||||
$(BIN)/test_def_const_fold \
|
||||
$(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \
|
||||
$(BIN)/test_amp_dot $(BIN)/test_arr_elem_field \
|
||||
$(BIN)/test_arr_elem_field_write \
|
||||
@@ -390,6 +391,12 @@ $(BIN)/test_def_neg_global: test/wcc/631_def_neg_global.c $(BIN)/ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_def_const_fold: test/wcc/732_def_const_fold.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_int_cast_signed: test/wcc/640_int_cast_signed.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
202
cmd/wcc/check.c
202
cmd/wcc/check.c
@@ -207,6 +207,32 @@ fold_int_literal(Node *n, u64 *out)
|
||||
}
|
||||
}
|
||||
|
||||
/* fold_binop — apply one constant binary op. The shared arithmetic
|
||||
* core of the two compile-time-int-eval paths: eval_enum_value (enum
|
||||
* member exprs) and eval_def_const (top-level def rhs, #88). Both
|
||||
* route here so wrap/shift/divide semantics are defined ONCE — rule
|
||||
* 10 demands the cstage and wwstage stamp the bit-identical literal,
|
||||
* and a single op table is the only way to keep them from drifting.
|
||||
* Returns 0 on division by zero or an op outside the constant subset;
|
||||
* the caller maps that to its own diagnostic. */
|
||||
static int
|
||||
fold_binop(Tkind op, u64 a, u64 b, u64 *out)
|
||||
{
|
||||
switch (op) {
|
||||
case TK_PLUS: *out = a + b; return 1;
|
||||
case TK_MINUS: *out = a - b; return 1;
|
||||
case TK_STAR: *out = a * b; return 1;
|
||||
case TK_SLASH: if (b == 0) return 0; *out = a / b; return 1;
|
||||
case TK_PERCENT: if (b == 0) return 0; *out = a % b; return 1;
|
||||
case TK_AMP: *out = a & b; return 1;
|
||||
case TK_PIPE: *out = a | b; return 1;
|
||||
case TK_CARET: *out = a ^ b; return 1;
|
||||
case TK_LSHIFT: *out = a << b; return 1;
|
||||
case TK_RSHIFT: *out = a >> b; return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* eval_enum_value — fold an enum member-value expression to a u64
|
||||
* constant. Sees prior siblings via the `prev` Tfield list (each
|
||||
* carries the member's name and resolved value in .offset). Returns
|
||||
@@ -238,28 +264,13 @@ eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
|
||||
if (!eval_enum_value(c, n->lhs, prev, &a) ||
|
||||
!eval_enum_value(c, n->rhs, prev, &b))
|
||||
return 0;
|
||||
switch (n->op) {
|
||||
case TK_PLUS: *out = a + b; return 1;
|
||||
case TK_MINUS: *out = a - b; return 1;
|
||||
case TK_STAR: *out = a * b; return 1;
|
||||
case TK_SLASH:
|
||||
if (b == 0) goto divzero;
|
||||
*out = a / b; return 1;
|
||||
case TK_PERCENT:
|
||||
if (b == 0) goto divzero;
|
||||
*out = a % b; return 1;
|
||||
case TK_AMP: *out = a & b; return 1;
|
||||
case TK_PIPE: *out = a | b; return 1;
|
||||
case TK_CARET: *out = a ^ b; return 1;
|
||||
case TK_LSHIFT: *out = a << b; return 1;
|
||||
case TK_RSHIFT: *out = a >> b; return 1;
|
||||
default:
|
||||
if (fold_binop(n->op, a, b, out))
|
||||
return 1;
|
||||
if ((n->op == TK_SLASH || n->op == TK_PERCENT) && b == 0)
|
||||
err(c, n->pos, "enum value: division by zero");
|
||||
else
|
||||
err(c, n->pos, "enum value: unsupported binary op %s",
|
||||
tokname(n->op));
|
||||
return 0;
|
||||
}
|
||||
divzero:
|
||||
err(c, n->pos, "enum value: division by zero");
|
||||
return 0;
|
||||
}
|
||||
case N_UN: {
|
||||
@@ -283,6 +294,146 @@ eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
|
||||
}
|
||||
}
|
||||
|
||||
/* def_cast_fits — for a def-rhs `value: T` cast strip (#88), does the
|
||||
* already-folded u64 `v` survive narrowing to integer target `t`?
|
||||
* Identity / widening / same-width casts always fit. A genuine
|
||||
* narrowing cast whose value falls outside the target's range must
|
||||
* NOT be silently truncated (rule 7 / drew): the caller turns a
|
||||
* miss into a loud error. Width comes from the type table (t->size,
|
||||
* rule 13) — never a hardcoded layout literal. Pure-u64 arithmetic
|
||||
* so the cstage and wwstage range check stay bit-identical (rule 10).
|
||||
* The `8`s here are CHAR_BIT and the u64 byte-width, not type-layout
|
||||
* sizes, so they are outside rule 13's scope. */
|
||||
static int
|
||||
def_cast_fits(Type *t, u64 v)
|
||||
{
|
||||
if (!type_isint(t)) return 1; /* non-int target: keep value as-is */
|
||||
u64 w = t->size;
|
||||
if (w >= 8) return 1; /* 64-bit target: no narrowing */
|
||||
u64 bits = w * 8;
|
||||
if (type_isunsigned(t))
|
||||
return (v >> bits) == 0;
|
||||
/* signed: truncate to `bits` then sign-extend; fits iff unchanged */
|
||||
u64 mask = ((u64)1 << bits) - 1;
|
||||
u64 sign = (u64)1 << (bits - 1);
|
||||
u64 ext = ((v & mask) ^ sign) - sign;
|
||||
return ext == v;
|
||||
}
|
||||
|
||||
/* eval_def_const — fold a top-level def's rhs to a u64 constant,
|
||||
* resolving sibling and imported def references, casts, and
|
||||
* arithmetic (#88). Reuses the shared fold_int_literal leaf/unary
|
||||
* fold and the fold_binop arith core; the ONLY thing it does that
|
||||
* eval_enum_value doesn't is resolve an identifier through the
|
||||
* checker's flat scope (scope_lookup_prefer for a bare sibling ref,
|
||||
* scope_lookup_in_module for a `mod.NAME` qualified ref) to the
|
||||
* referent def's own rhs, then recurse.
|
||||
*
|
||||
* Why this stays a distinct evaluator from eval_enum_value rather
|
||||
* than a full merge (rule 8 WHY): enum-member eval carries implicit
|
||||
* prev+1 auto-increment and forward-only sibling lookup over a Tfield
|
||||
* chain; def eval has neither — it resolves through the scope/decl
|
||||
* graph, which can reference forward and across modules. The two
|
||||
* lookup models don't reconcile cleanly, so they share the arith
|
||||
* core (fold_binop) + leaf fold (fold_int_literal) and keep separate
|
||||
* top-level shapes.
|
||||
*
|
||||
* `depth` bounds a def->def->def chain; a cycle (def A = B; def B = A,
|
||||
* incl. cross-module) hits the cap and fails loud rather than hanging
|
||||
* (rule 7), mirroring the cgen.c nsteps>=16 abort precedent. */
|
||||
static int
|
||||
eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
{
|
||||
if (n == NULL) return 0;
|
||||
if (depth >= 16) {
|
||||
err(c, n->pos,
|
||||
"def value: reference chain too deep (cycle?)");
|
||||
return 0;
|
||||
}
|
||||
if (fold_int_literal(n, out)) return 1;
|
||||
switch (n->kind) {
|
||||
case N_BIN: {
|
||||
u64 a, b;
|
||||
if (!eval_def_const(c, n->lhs, &a, depth + 1) ||
|
||||
!eval_def_const(c, n->rhs, &b, depth + 1))
|
||||
return 0;
|
||||
if (fold_binop(n->op, a, b, out))
|
||||
return 1;
|
||||
if ((n->op == TK_SLASH || n->op == TK_PERCENT) && b == 0)
|
||||
err(c, n->pos, "def value: division by zero");
|
||||
else
|
||||
err(c, n->pos, "def value: unsupported binary op %s",
|
||||
tokname(n->op));
|
||||
return 0;
|
||||
}
|
||||
case N_UN: {
|
||||
/* fold_int_literal already covers unary-over-leaf; this
|
||||
* arm catches unary over a resolved ref, e.g. `-A`. */
|
||||
u64 v;
|
||||
if (!eval_def_const(c, n->lhs, &v, depth + 1)) return 0;
|
||||
switch (n->op) {
|
||||
case TK_MINUS: *out = (u64)(-(i64)v); return 1;
|
||||
case TK_TILDE: *out = ~v; return 1;
|
||||
case TK_PLUS: *out = v; return 1;
|
||||
default:
|
||||
err(c, n->pos,
|
||||
"def value: unsupported unary op %s",
|
||||
tokname(n->op));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
case N_CAST: {
|
||||
/* lhs = value, n->type = resolved target (set by cexpr's
|
||||
* N_CAST arm in pass 2). Strip the cast, keeping the value;
|
||||
* a narrowing cast that loses the value fails loud. */
|
||||
u64 v;
|
||||
if (!eval_def_const(c, n->lhs, &v, depth + 1)) return 0;
|
||||
if (!def_cast_fits(n->type, v)) {
|
||||
err(c, n->pos,
|
||||
"def value: narrowing cast loses value");
|
||||
return 0;
|
||||
}
|
||||
*out = v;
|
||||
return 1;
|
||||
}
|
||||
case N_IDENT: {
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, n->str);
|
||||
if (s == NULL || s->kind != SK_DEF ||
|
||||
s->decl == NULL || s->decl->rhs == NULL)
|
||||
return 0;
|
||||
return eval_def_const(c, s->decl->rhs, out, depth + 1);
|
||||
}
|
||||
case N_DOT: {
|
||||
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
|
||||
Sym *s = scope_lookup_in_module(c->cur, n->lhs->str, n->str);
|
||||
if (s == NULL || s->kind != SK_DEF ||
|
||||
s->decl == NULL || s->decl->rhs == NULL)
|
||||
return 0;
|
||||
return eval_def_const(c, s->decl->rhs, out, depth + 1);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* stamp_intlit — rewrite a const-folded def rhs in place to the
|
||||
* literal it evaluates to, preserving the node's cexpr-resolved type
|
||||
* so the downstream DATA-row emit width and the invariant checks see
|
||||
* a properly-typed literal leaf. Lets cgen's existing literal-only
|
||||
* fold lay down the row with no codegen change (#88). */
|
||||
static void
|
||||
stamp_intlit(Checker *c, Node *n, u64 v)
|
||||
{
|
||||
n->kind = N_INTLIT;
|
||||
n->uval = v;
|
||||
n->op = 0;
|
||||
n->str = aprintf(c->a, "%llu", (unsigned long long)v);
|
||||
n->strlen = strlen(n->str);
|
||||
n->lhs = n->rhs = n->cond = n->body = n->els = n->list = NULL;
|
||||
n->tsuffix = NULL;
|
||||
/* n->type left intact (the type cexpr inferred for the rhs). */
|
||||
}
|
||||
|
||||
static Type *
|
||||
resolve_type(Checker *c, Node *n)
|
||||
{
|
||||
@@ -2013,6 +2164,17 @@ check_file(Checker *c, Node *file)
|
||||
err(c, d->pos, "def %s init %s not assignable to %s",
|
||||
d->str, type_name(c->a, rt),
|
||||
type_name(c->a, d->type));
|
||||
/* #88: const-fold sibling/imported def refs,
|
||||
* casts, and arithmetic so cgen's literal-only
|
||||
* emit can lay down the DATA row. GATED on the
|
||||
* plain literal fold missing first, so existing
|
||||
* literal/unary defs keep their rhs node and the
|
||||
* emitted bytes stay byte-identical. */
|
||||
u64 dv;
|
||||
if (rt != ty_err
|
||||
&& !fold_int_literal(d->rhs, &dv)
|
||||
&& eval_def_const(c, d->rhs, &dv, 0))
|
||||
stamp_intlit(c, d->rhs, dv);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8060,6 +8060,174 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = {
|
||||
n.tsuffix = empty;
|
||||
};
|
||||
|
||||
// foldbinop — shared constant binary-op core for the two compile-time
|
||||
// integer evaluators in this file: enumvalfold (enum member exprs) and
|
||||
// evaldefconst (top-level def rhs, #88). One op table so the cstage
|
||||
// (cmd/wcc/check.c fold_binop) and wwstage stamp the bit-identical
|
||||
// literal — rule 10 lives at the check pass for #88. Returns false on
|
||||
// division by zero or an op outside the constant subset; the caller
|
||||
// maps that to its own diagnostic.
|
||||
fn foldbinop(op: tkind, a: u64, b: u64, out: *u64) bool = {
|
||||
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
||||
if (op == tkind.TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == tkind.TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
||||
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// deffolderr — loud diagnostic + checker error count bump for an
|
||||
// unfoldable def rhs (cycle / narrowing-cast / bad op). c.errs > 0
|
||||
// gates cgen off in main.ww:165, so this fails the build rather than
|
||||
// emitting a missing DATA row silently (rule 7). cstage twin: err()
|
||||
// in cmd/wcc/check.c.
|
||||
fn deffolderr(c: *checker, n: *node, msg: str) void = {
|
||||
os.write(2, n.file.ptr, n.file.len: u64);
|
||||
os.write(2, ": error: ".ptr, 9u64);
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// defcastfits — wwstage twin of cstage def_cast_fits (cmd/wcc/check.c):
|
||||
// does the folded u64 `v` survive narrowing to integer target `t`?
|
||||
// Identity / widening / same-width casts always fit; a genuine
|
||||
// narrowing cast whose value falls outside the target range must NOT
|
||||
// be silently truncated (rule 7 / drew). Width via the type table
|
||||
// (t.size, rule 13); the 8s are CHAR_BIT and the u64 byte-width, not
|
||||
// type-layout sizes, so they sit outside rule 13's scope. Pure-u64 so
|
||||
// the range check is bit-identical to cstage (rule 10).
|
||||
fn defcastfits(t: *tinfo, v: u64) bool = {
|
||||
if (!typeisint(t)) { return true; }; // non-int target: keep value
|
||||
let w: u64 = t.size;
|
||||
if (w >= 8u64) { return true; }; // 64-bit target: no narrowing
|
||||
let bits: u64 = w * 8u64;
|
||||
if (typeisunsigned(t)) { return (v >> bits) == 0u64; };
|
||||
// signed: truncate to `bits` then sign-extend; fits iff unchanged
|
||||
let mask: u64 = (1u64 << bits) - 1u64;
|
||||
let sign: u64 = 1u64 << (bits - 1u64);
|
||||
let ext: u64 = ((v & mask) ^ sign) - sign;
|
||||
return ext == v;
|
||||
};
|
||||
|
||||
// evaldefconst — fold a top-level def's rhs to a u64 constant,
|
||||
// resolving sibling and imported def references, casts, and
|
||||
// arithmetic (#88). Reuses foldintliteral (leaf/unary) + foldbinop
|
||||
// (arith); the ONLY thing it does that enumvalfold doesn't is resolve
|
||||
// an identifier through the checker's flat scope (scopelookupprefer
|
||||
// for a bare sibling ref, scopelookupinmodule for `mod.NAME`) to the
|
||||
// referent def's own rhs, then recurse.
|
||||
//
|
||||
// Why this stays distinct from enumvalfold rather than a full merge
|
||||
// (rule 8 WHY): enum-member eval carries implicit prev+1 auto-increment
|
||||
// and forward-only sibling lookup over the member chain; def eval has
|
||||
// neither — it resolves through the scope/decl graph, which references
|
||||
// forward and across modules. The two lookup models don't reconcile
|
||||
// cleanly, so they share the arith core (foldbinop) + leaf fold
|
||||
// (foldintliteral) and keep separate top-level shapes.
|
||||
//
|
||||
// `depth` bounds a def->def->def chain; a cycle (def A = B; def B = A,
|
||||
// incl. cross-module) hits the cap and fails loud rather than hanging
|
||||
// (rule 7), mirroring the cgen nsteps>=16 abort precedent.
|
||||
fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (depth >= 16) {
|
||||
deffolderr(c, n, "def value: reference chain too deep (cycle?)");
|
||||
return false;
|
||||
};
|
||||
if (foldintliteral(n, out)) { return true; };
|
||||
let k: nkind = n.kind;
|
||||
if (k == nkind.N_BIN) {
|
||||
let a: u64 = 0u64;
|
||||
let b: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &a, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.rhs, &b, depth + 1)) { return false; };
|
||||
if (foldbinop(n.op, a, b, out)) { return true; };
|
||||
if ((n.op == tkind.TK_SLASH || n.op == tkind.TK_PERCENT) && b == 0u64) {
|
||||
deffolderr(c, n, "def value: division by zero");
|
||||
} else {
|
||||
deffolderr(c, n, "def value: unsupported binary op");
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_UN) {
|
||||
// foldintliteral already covers unary-over-leaf; this arm
|
||||
// catches unary over a resolved ref, e.g. `-A`.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
if (n.op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (n.op == tkind.TK_TILDE) { *out = ~v; return true; };
|
||||
if (n.op == tkind.TK_PLUS) { *out = v; return true; };
|
||||
deffolderr(c, n, "def value: unsupported unary op");
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_CAST) {
|
||||
// n.lhs = value; n.type_ = resolved target (stamped by
|
||||
// exprtype's N_CAST arm during resolvewalk). Strip the cast
|
||||
// keeping the value; a narrowing cast that loses it fails loud.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
let t: *tinfo = (n.type_): *tinfo;
|
||||
if (!defcastfits(t, v)) {
|
||||
deffolderr(c, n, "def value: narrowing cast loses value");
|
||||
return false;
|
||||
};
|
||||
*out = v;
|
||||
return true;
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
if (s.decl.rhs == nil) { return false; };
|
||||
return evaldefconst(c, s.decl.rhs, out, depth + 1);
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
if (s.decl.rhs == nil) { return false; };
|
||||
return evaldefconst(c, s.decl.rhs, out, depth + 1);
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// stampintlit — rewrite a const-folded def rhs in place to its literal
|
||||
// value, preserving the node's resolved type_ so the DATA-row emit
|
||||
// width and pass-3 asserttyped see a properly-typed literal leaf. Lets
|
||||
// cgen's existing emitdefconstants lay down the row with no codegen
|
||||
// change (#88). Shape mirrors foldtointlit (the #42 stamp).
|
||||
fn stampintlit(n: *node, v: u64) void = {
|
||||
n.kind = nkind.N_INTLIT;
|
||||
n.uval = v;
|
||||
n.op = tkind.TK_NONE;
|
||||
n.str = arenau64tos(v);
|
||||
n.lhs = nil;
|
||||
n.rhs = nil;
|
||||
n.cond = nil;
|
||||
n.body = nil;
|
||||
n.els = nil;
|
||||
n.list = nil;
|
||||
let empty: str;
|
||||
n.tsuffix = empty;
|
||||
// n.type_ left intact (the type exprtype inferred for the rhs).
|
||||
};
|
||||
|
||||
// enumvalfold — fold an enum member's value expression to a u64
|
||||
// constant. The Hare-fidelity set: literal leaves, unary +/-/~,
|
||||
// binary arithmetic (+ - * / %), bitwise (& | ^), shifts (<< >>),
|
||||
@@ -8105,24 +8273,7 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
|
||||
let b: u64 = 0u64;
|
||||
if (!enumvalfold(body, until, e.lhs, &a)) { return false; };
|
||||
if (!enumvalfold(body, until, e.rhs, &b)) { return false; };
|
||||
let op: tkind = e.op;
|
||||
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
||||
if (op == tkind.TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == tkind.TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
||||
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
return foldbinop(e.op, a, b, out);
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let prev: u64 = (-1i64): u64;
|
||||
@@ -10273,6 +10424,19 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
} else { if (k == nkind.N_DEF) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
|
||||
// #88: const-fold sibling/imported def refs, casts, and
|
||||
// arithmetic so cgen's literal-only emitdefconstants can
|
||||
// lay down the DATA row. GATED on the plain literal fold
|
||||
// missing first, so existing literal/unary defs keep
|
||||
// their rhs node and the emitted bytes stay byte-identical.
|
||||
if (d.rhs != nil) {
|
||||
let dv: u64 = 0u64;
|
||||
if (!foldintliteral(d.rhs, &dv)) {
|
||||
if (evaldefconst(c, d.rhs, &dv, 0)) {
|
||||
stampintlit(d.rhs, dv);
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (k == nkind.N_TYPEDECL) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
} else { if (k == nkind.N_LET) {
|
||||
|
||||
@@ -949,6 +949,174 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = {
|
||||
n.tsuffix = empty;
|
||||
};
|
||||
|
||||
// foldbinop — shared constant binary-op core for the two compile-time
|
||||
// integer evaluators in this file: enumvalfold (enum member exprs) and
|
||||
// evaldefconst (top-level def rhs, #88). One op table so the cstage
|
||||
// (cmd/wcc/check.c fold_binop) and wwstage stamp the bit-identical
|
||||
// literal — rule 10 lives at the check pass for #88. Returns false on
|
||||
// division by zero or an op outside the constant subset; the caller
|
||||
// maps that to its own diagnostic.
|
||||
fn foldbinop(op: tkind, a: u64, b: u64, out: *u64) bool = {
|
||||
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
||||
if (op == tkind.TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == tkind.TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
||||
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// deffolderr — loud diagnostic + checker error count bump for an
|
||||
// unfoldable def rhs (cycle / narrowing-cast / bad op). c.errs > 0
|
||||
// gates cgen off in main.ww:165, so this fails the build rather than
|
||||
// emitting a missing DATA row silently (rule 7). cstage twin: err()
|
||||
// in cmd/wcc/check.c.
|
||||
fn deffolderr(c: *checker, n: *node, msg: str) void = {
|
||||
os.write(2, n.file.ptr, n.file.len: u64);
|
||||
os.write(2, ": error: ".ptr, 9u64);
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// defcastfits — wwstage twin of cstage def_cast_fits (cmd/wcc/check.c):
|
||||
// does the folded u64 `v` survive narrowing to integer target `t`?
|
||||
// Identity / widening / same-width casts always fit; a genuine
|
||||
// narrowing cast whose value falls outside the target range must NOT
|
||||
// be silently truncated (rule 7 / drew). Width via the type table
|
||||
// (t.size, rule 13); the 8s are CHAR_BIT and the u64 byte-width, not
|
||||
// type-layout sizes, so they sit outside rule 13's scope. Pure-u64 so
|
||||
// the range check is bit-identical to cstage (rule 10).
|
||||
fn defcastfits(t: *tinfo, v: u64) bool = {
|
||||
if (!typeisint(t)) { return true; }; // non-int target: keep value
|
||||
let w: u64 = t.size;
|
||||
if (w >= 8u64) { return true; }; // 64-bit target: no narrowing
|
||||
let bits: u64 = w * 8u64;
|
||||
if (typeisunsigned(t)) { return (v >> bits) == 0u64; };
|
||||
// signed: truncate to `bits` then sign-extend; fits iff unchanged
|
||||
let mask: u64 = (1u64 << bits) - 1u64;
|
||||
let sign: u64 = 1u64 << (bits - 1u64);
|
||||
let ext: u64 = ((v & mask) ^ sign) - sign;
|
||||
return ext == v;
|
||||
};
|
||||
|
||||
// evaldefconst — fold a top-level def's rhs to a u64 constant,
|
||||
// resolving sibling and imported def references, casts, and
|
||||
// arithmetic (#88). Reuses foldintliteral (leaf/unary) + foldbinop
|
||||
// (arith); the ONLY thing it does that enumvalfold doesn't is resolve
|
||||
// an identifier through the checker's flat scope (scopelookupprefer
|
||||
// for a bare sibling ref, scopelookupinmodule for `mod.NAME`) to the
|
||||
// referent def's own rhs, then recurse.
|
||||
//
|
||||
// Why this stays distinct from enumvalfold rather than a full merge
|
||||
// (rule 8 WHY): enum-member eval carries implicit prev+1 auto-increment
|
||||
// and forward-only sibling lookup over the member chain; def eval has
|
||||
// neither — it resolves through the scope/decl graph, which references
|
||||
// forward and across modules. The two lookup models don't reconcile
|
||||
// cleanly, so they share the arith core (foldbinop) + leaf fold
|
||||
// (foldintliteral) and keep separate top-level shapes.
|
||||
//
|
||||
// `depth` bounds a def->def->def chain; a cycle (def A = B; def B = A,
|
||||
// incl. cross-module) hits the cap and fails loud rather than hanging
|
||||
// (rule 7), mirroring the cgen nsteps>=16 abort precedent.
|
||||
fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (depth >= 16) {
|
||||
deffolderr(c, n, "def value: reference chain too deep (cycle?)");
|
||||
return false;
|
||||
};
|
||||
if (foldintliteral(n, out)) { return true; };
|
||||
let k: nkind = n.kind;
|
||||
if (k == nkind.N_BIN) {
|
||||
let a: u64 = 0u64;
|
||||
let b: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &a, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.rhs, &b, depth + 1)) { return false; };
|
||||
if (foldbinop(n.op, a, b, out)) { return true; };
|
||||
if ((n.op == tkind.TK_SLASH || n.op == tkind.TK_PERCENT) && b == 0u64) {
|
||||
deffolderr(c, n, "def value: division by zero");
|
||||
} else {
|
||||
deffolderr(c, n, "def value: unsupported binary op");
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_UN) {
|
||||
// foldintliteral already covers unary-over-leaf; this arm
|
||||
// catches unary over a resolved ref, e.g. `-A`.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
if (n.op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (n.op == tkind.TK_TILDE) { *out = ~v; return true; };
|
||||
if (n.op == tkind.TK_PLUS) { *out = v; return true; };
|
||||
deffolderr(c, n, "def value: unsupported unary op");
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_CAST) {
|
||||
// n.lhs = value; n.type_ = resolved target (stamped by
|
||||
// exprtype's N_CAST arm during resolvewalk). Strip the cast
|
||||
// keeping the value; a narrowing cast that loses it fails loud.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
let t: *tinfo = (n.type_): *tinfo;
|
||||
if (!defcastfits(t, v)) {
|
||||
deffolderr(c, n, "def value: narrowing cast loses value");
|
||||
return false;
|
||||
};
|
||||
*out = v;
|
||||
return true;
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
if (s.decl.rhs == nil) { return false; };
|
||||
return evaldefconst(c, s.decl.rhs, out, depth + 1);
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
if (s.decl.rhs == nil) { return false; };
|
||||
return evaldefconst(c, s.decl.rhs, out, depth + 1);
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// stampintlit — rewrite a const-folded def rhs in place to its literal
|
||||
// value, preserving the node's resolved type_ so the DATA-row emit
|
||||
// width and pass-3 asserttyped see a properly-typed literal leaf. Lets
|
||||
// cgen's existing emitdefconstants lay down the row with no codegen
|
||||
// change (#88). Shape mirrors foldtointlit (the #42 stamp).
|
||||
fn stampintlit(n: *node, v: u64) void = {
|
||||
n.kind = nkind.N_INTLIT;
|
||||
n.uval = v;
|
||||
n.op = tkind.TK_NONE;
|
||||
n.str = arenau64tos(v);
|
||||
n.lhs = nil;
|
||||
n.rhs = nil;
|
||||
n.cond = nil;
|
||||
n.body = nil;
|
||||
n.els = nil;
|
||||
n.list = nil;
|
||||
let empty: str;
|
||||
n.tsuffix = empty;
|
||||
// n.type_ left intact (the type exprtype inferred for the rhs).
|
||||
};
|
||||
|
||||
// enumvalfold — fold an enum member's value expression to a u64
|
||||
// constant. The Hare-fidelity set: literal leaves, unary +/-/~,
|
||||
// binary arithmetic (+ - * / %), bitwise (& | ^), shifts (<< >>),
|
||||
@@ -994,24 +1162,7 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
|
||||
let b: u64 = 0u64;
|
||||
if (!enumvalfold(body, until, e.lhs, &a)) { return false; };
|
||||
if (!enumvalfold(body, until, e.rhs, &b)) { return false; };
|
||||
let op: tkind = e.op;
|
||||
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
||||
if (op == tkind.TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == tkind.TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
||||
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
return foldbinop(e.op, a, b, out);
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let prev: u64 = (-1i64): u64;
|
||||
@@ -3162,6 +3313,19 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
} else { if (k == nkind.N_DEF) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
|
||||
// #88: const-fold sibling/imported def refs, casts, and
|
||||
// arithmetic so cgen's literal-only emitdefconstants can
|
||||
// lay down the DATA row. GATED on the plain literal fold
|
||||
// missing first, so existing literal/unary defs keep
|
||||
// their rhs node and the emitted bytes stay byte-identical.
|
||||
if (d.rhs != nil) {
|
||||
let dv: u64 = 0u64;
|
||||
if (!foldintliteral(d.rhs, &dv)) {
|
||||
if (evaldefconst(c, d.rhs, &dv, 0)) {
|
||||
stampintlit(d.rhs, dv);
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (k == nkind.N_TYPEDECL) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
} else { if (k == nkind.N_LET) {
|
||||
|
||||
@@ -8060,6 +8060,174 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = {
|
||||
n.tsuffix = empty;
|
||||
};
|
||||
|
||||
// foldbinop — shared constant binary-op core for the two compile-time
|
||||
// integer evaluators in this file: enumvalfold (enum member exprs) and
|
||||
// evaldefconst (top-level def rhs, #88). One op table so the cstage
|
||||
// (cmd/wcc/check.c fold_binop) and wwstage stamp the bit-identical
|
||||
// literal — rule 10 lives at the check pass for #88. Returns false on
|
||||
// division by zero or an op outside the constant subset; the caller
|
||||
// maps that to its own diagnostic.
|
||||
fn foldbinop(op: tkind, a: u64, b: u64, out: *u64) bool = {
|
||||
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
||||
if (op == tkind.TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == tkind.TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
||||
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// deffolderr — loud diagnostic + checker error count bump for an
|
||||
// unfoldable def rhs (cycle / narrowing-cast / bad op). c.errs > 0
|
||||
// gates cgen off in main.ww:165, so this fails the build rather than
|
||||
// emitting a missing DATA row silently (rule 7). cstage twin: err()
|
||||
// in cmd/wcc/check.c.
|
||||
fn deffolderr(c: *checker, n: *node, msg: str) void = {
|
||||
os.write(2, n.file.ptr, n.file.len: u64);
|
||||
os.write(2, ": error: ".ptr, 9u64);
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// defcastfits — wwstage twin of cstage def_cast_fits (cmd/wcc/check.c):
|
||||
// does the folded u64 `v` survive narrowing to integer target `t`?
|
||||
// Identity / widening / same-width casts always fit; a genuine
|
||||
// narrowing cast whose value falls outside the target range must NOT
|
||||
// be silently truncated (rule 7 / drew). Width via the type table
|
||||
// (t.size, rule 13); the 8s are CHAR_BIT and the u64 byte-width, not
|
||||
// type-layout sizes, so they sit outside rule 13's scope. Pure-u64 so
|
||||
// the range check is bit-identical to cstage (rule 10).
|
||||
fn defcastfits(t: *tinfo, v: u64) bool = {
|
||||
if (!typeisint(t)) { return true; }; // non-int target: keep value
|
||||
let w: u64 = t.size;
|
||||
if (w >= 8u64) { return true; }; // 64-bit target: no narrowing
|
||||
let bits: u64 = w * 8u64;
|
||||
if (typeisunsigned(t)) { return (v >> bits) == 0u64; };
|
||||
// signed: truncate to `bits` then sign-extend; fits iff unchanged
|
||||
let mask: u64 = (1u64 << bits) - 1u64;
|
||||
let sign: u64 = 1u64 << (bits - 1u64);
|
||||
let ext: u64 = ((v & mask) ^ sign) - sign;
|
||||
return ext == v;
|
||||
};
|
||||
|
||||
// evaldefconst — fold a top-level def's rhs to a u64 constant,
|
||||
// resolving sibling and imported def references, casts, and
|
||||
// arithmetic (#88). Reuses foldintliteral (leaf/unary) + foldbinop
|
||||
// (arith); the ONLY thing it does that enumvalfold doesn't is resolve
|
||||
// an identifier through the checker's flat scope (scopelookupprefer
|
||||
// for a bare sibling ref, scopelookupinmodule for `mod.NAME`) to the
|
||||
// referent def's own rhs, then recurse.
|
||||
//
|
||||
// Why this stays distinct from enumvalfold rather than a full merge
|
||||
// (rule 8 WHY): enum-member eval carries implicit prev+1 auto-increment
|
||||
// and forward-only sibling lookup over the member chain; def eval has
|
||||
// neither — it resolves through the scope/decl graph, which references
|
||||
// forward and across modules. The two lookup models don't reconcile
|
||||
// cleanly, so they share the arith core (foldbinop) + leaf fold
|
||||
// (foldintliteral) and keep separate top-level shapes.
|
||||
//
|
||||
// `depth` bounds a def->def->def chain; a cycle (def A = B; def B = A,
|
||||
// incl. cross-module) hits the cap and fails loud rather than hanging
|
||||
// (rule 7), mirroring the cgen nsteps>=16 abort precedent.
|
||||
fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (depth >= 16) {
|
||||
deffolderr(c, n, "def value: reference chain too deep (cycle?)");
|
||||
return false;
|
||||
};
|
||||
if (foldintliteral(n, out)) { return true; };
|
||||
let k: nkind = n.kind;
|
||||
if (k == nkind.N_BIN) {
|
||||
let a: u64 = 0u64;
|
||||
let b: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &a, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.rhs, &b, depth + 1)) { return false; };
|
||||
if (foldbinop(n.op, a, b, out)) { return true; };
|
||||
if ((n.op == tkind.TK_SLASH || n.op == tkind.TK_PERCENT) && b == 0u64) {
|
||||
deffolderr(c, n, "def value: division by zero");
|
||||
} else {
|
||||
deffolderr(c, n, "def value: unsupported binary op");
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_UN) {
|
||||
// foldintliteral already covers unary-over-leaf; this arm
|
||||
// catches unary over a resolved ref, e.g. `-A`.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
if (n.op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (n.op == tkind.TK_TILDE) { *out = ~v; return true; };
|
||||
if (n.op == tkind.TK_PLUS) { *out = v; return true; };
|
||||
deffolderr(c, n, "def value: unsupported unary op");
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_CAST) {
|
||||
// n.lhs = value; n.type_ = resolved target (stamped by
|
||||
// exprtype's N_CAST arm during resolvewalk). Strip the cast
|
||||
// keeping the value; a narrowing cast that loses it fails loud.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
let t: *tinfo = (n.type_): *tinfo;
|
||||
if (!defcastfits(t, v)) {
|
||||
deffolderr(c, n, "def value: narrowing cast loses value");
|
||||
return false;
|
||||
};
|
||||
*out = v;
|
||||
return true;
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
if (s.decl.rhs == nil) { return false; };
|
||||
return evaldefconst(c, s.decl.rhs, out, depth + 1);
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
if (s.decl.rhs == nil) { return false; };
|
||||
return evaldefconst(c, s.decl.rhs, out, depth + 1);
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// stampintlit — rewrite a const-folded def rhs in place to its literal
|
||||
// value, preserving the node's resolved type_ so the DATA-row emit
|
||||
// width and pass-3 asserttyped see a properly-typed literal leaf. Lets
|
||||
// cgen's existing emitdefconstants lay down the row with no codegen
|
||||
// change (#88). Shape mirrors foldtointlit (the #42 stamp).
|
||||
fn stampintlit(n: *node, v: u64) void = {
|
||||
n.kind = nkind.N_INTLIT;
|
||||
n.uval = v;
|
||||
n.op = tkind.TK_NONE;
|
||||
n.str = arenau64tos(v);
|
||||
n.lhs = nil;
|
||||
n.rhs = nil;
|
||||
n.cond = nil;
|
||||
n.body = nil;
|
||||
n.els = nil;
|
||||
n.list = nil;
|
||||
let empty: str;
|
||||
n.tsuffix = empty;
|
||||
// n.type_ left intact (the type exprtype inferred for the rhs).
|
||||
};
|
||||
|
||||
// enumvalfold — fold an enum member's value expression to a u64
|
||||
// constant. The Hare-fidelity set: literal leaves, unary +/-/~,
|
||||
// binary arithmetic (+ - * / %), bitwise (& | ^), shifts (<< >>),
|
||||
@@ -8105,24 +8273,7 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
|
||||
let b: u64 = 0u64;
|
||||
if (!enumvalfold(body, until, e.lhs, &a)) { return false; };
|
||||
if (!enumvalfold(body, until, e.rhs, &b)) { return false; };
|
||||
let op: tkind = e.op;
|
||||
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
||||
if (op == tkind.TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == tkind.TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
||||
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
return foldbinop(e.op, a, b, out);
|
||||
};
|
||||
if (k == nkind.N_IDENT) {
|
||||
let prev: u64 = (-1i64): u64;
|
||||
@@ -10273,6 +10424,19 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
} else { if (k == nkind.N_DEF) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
|
||||
// #88: const-fold sibling/imported def refs, casts, and
|
||||
// arithmetic so cgen's literal-only emitdefconstants can
|
||||
// lay down the DATA row. GATED on the plain literal fold
|
||||
// missing first, so existing literal/unary defs keep
|
||||
// their rhs node and the emitted bytes stay byte-identical.
|
||||
if (d.rhs != nil) {
|
||||
let dv: u64 = 0u64;
|
||||
if (!foldintliteral(d.rhs, &dv)) {
|
||||
if (evaldefconst(c, d.rhs, &dv, 0)) {
|
||||
stampintlit(d.rhs, dv);
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (k == nkind.N_TYPEDECL) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
} else { if (k == nkind.N_LET) {
|
||||
|
||||
438
test/wcc/732_def_const_fold.c
Normal file
438
test/wcc/732_def_const_fold.c
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 732_def_const_fold — top-level `def` rhs const-fold extended to
|
||||
* sibling/imported def references, casts, and arithmetic (PROJECT #88).
|
||||
*
|
||||
* Predecessor 631_def_neg_global lifted the LITERAL fold (INTLIT/RUNELIT/
|
||||
* TRUE/FALSE/NIL + unary +/-/~) into the shared fold_int_literal /
|
||||
* foldintliteral helper; it explicitly left N_CAST, sibling-ident, and
|
||||
* N_BIN OUT OF SCOPE (see 631's header, "negative-u32-cast"). #88 brings
|
||||
* them in: a Hare-faithful `def SCHAR_MAX = types::I8_MAX;` or
|
||||
* `def MASK: i32 = (1 << 7) - 1;` was previously inexpressible — the rhs
|
||||
* folded to nothing, no DATA row was emitted, and any reference failed
|
||||
* to link.
|
||||
*
|
||||
* The fix folds at CHECK time (both stages' pass-2 N_DEF arm) via a new
|
||||
* eval_def_const / evaldefconst that reuses the shared fold_binop arith
|
||||
* core (factored out of eval_enum_value / enumvalfold) and the checker's
|
||||
* EXISTING scope lookup (scope_lookup_prefer for a sibling ref,
|
||||
* scope_lookup_in_module for a `mod.NAME` qualified ref). On success the
|
||||
* rhs is stamped to an N_INTLIT in place, so cgen's literal-only
|
||||
* emit_defs / emitdefconstants lays down the DATA row with no codegen
|
||||
* change. The fold is GATED on the plain literal fold missing first, so
|
||||
* every pre-#88 def keeps its node and the emitted bytes stay
|
||||
* byte-identical (the bootstrap has zero def-ref-def, so 990-997 never
|
||||
* touch this latent path — hence this targeted fixture).
|
||||
*
|
||||
* Coverage:
|
||||
* 1. EXEC (same-module, via the `ww` driver, + `ww_ww` if built):
|
||||
* sibling-ref, sibling+arith, def->def->def chain, shift+arith,
|
||||
* and a widening cast — each read back at a use site to prove the
|
||||
* DATA row links and carries the right value.
|
||||
* 2. BYTE-ID: cstage w6c vs wwstage w6c_ww `.s` for every exec row.
|
||||
* 3. CROSS-MODULE (#88 a2): a combined multi-`package` source (the
|
||||
* driver's internal concat form, fed straight to w6c like
|
||||
* 728_match_4arm_cross_module) where `def K = a.J + 1;` resolves
|
||||
* the imported `a.J`. Assert the folded DATA row value AND byte-id.
|
||||
* 4. FAIL-LOUD (rule 7): a same-module cycle (def A=B; def B=A), a
|
||||
* cross-module cycle, and a narrowing cast whose value overflows
|
||||
* the target must each fail the build on BOTH stages (never a
|
||||
* silent missing row / silent truncation).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---- 1. same-module EXEC rows (compile+link+run) -------------------- */
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row exec_rows[] = {
|
||||
/* Bare sibling reference: `def B = A;`. Pre-#88 the rhs (N_IDENT)
|
||||
* folded to nothing. */
|
||||
{ "sibling-ref",
|
||||
"def A: i32 = 7;\n"
|
||||
"def B: i32 = A;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet x: i32 = B;\n"
|
||||
"\tif (x == 7) { return 42; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* Sibling + arithmetic: the headline `def B = A + 1;`. */
|
||||
{ "sibling-add",
|
||||
"def A: i32 = 5;\n"
|
||||
"def B: i32 = A + 1;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet x: i32 = B;\n"
|
||||
"\tif (x == 6) { return 42; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* def -> def -> def chain: C resolves through B through A. Pins
|
||||
* the recursive resolve (and that the depth guard doesn't trip on
|
||||
* a legitimate short chain). */
|
||||
{ "def-chain",
|
||||
"def A: i32 = 5;\n"
|
||||
"def B: i32 = A + 1;\n"
|
||||
"def C: i32 = B * A;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet x: i32 = C;\n"
|
||||
"\tif (x == 30) { return 42; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* Pure arithmetic with a shift: `(1 << 7) - 1` == 127. Exercises
|
||||
* the fold_binop shift + subtract path off any sibling ref. */
|
||||
{ "shift-arith",
|
||||
"def MASK: i32 = (1 << 7) - 1;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet x: i32 = MASK;\n"
|
||||
"\tif (x == 127) { return 42; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* Widening cast over a sibling ref: `A: i64` where A: i32 = 5.
|
||||
* The N_CAST is stripped (i32->i64 widens, value fits), so W folds
|
||||
* to 5 in an 8-byte slot. */
|
||||
{ "widening-cast",
|
||||
"def A: i32 = 5;\n"
|
||||
"def W: i64 = A: i64;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet x: i64 = W;\n"
|
||||
"\tif (x == 5i64) { return 42; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/dcf_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dcf_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
/* timeout 180 per repo convention (990-997); test/run does not bound
|
||||
* individual binaries, so an unguarded hang would stall make test. */
|
||||
snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* ---- shared .s emit + slurp ----------------------------------------- */
|
||||
|
||||
static int
|
||||
emit_s(const char *w6c, const char *src, char *out_s, size_t cap)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "timeout 180 %s -o %s %s 2>/dev/null",
|
||||
w6c, out_s, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
slurp(const char *path, char *buf, size_t cap)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
size_t n = fread(buf, 1, cap - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
/* ---- 2. byte-identity of cstage vs wwstage .s ----------------------- */
|
||||
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const char *src, const char *label, int i)
|
||||
{
|
||||
char wwsrc[64], cs[64], ws[64];
|
||||
snprintf(wwsrc, sizeof wwsrc, "/tmp/dcf_bi_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/dcf_bi_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/dcf_bi_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(wwsrc, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
char w6c[640], w6c_ww[640];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
if (emit_s(w6c, wwsrc, cs, sizeof cs) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", label);
|
||||
unlink(wwsrc);
|
||||
return -1;
|
||||
}
|
||||
if (emit_s(w6c_ww, wwsrc, ws, sizeof ws) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", label);
|
||||
unlink(wwsrc); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
label);
|
||||
unlink(wwsrc); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---- 3. cross-module fold: a combined multi-package source ----------
|
||||
* Fed straight to w6c (the driver's internal concat form — `ww build`
|
||||
* can't expand an already-concatenated source). `def K = a.J + 1;`
|
||||
* resolves the imported `a.J` (41) through scope_lookup_in_module and
|
||||
* folds to 42, whose DATA row first byte is '*' (0x2a). */
|
||||
static const char xmod_src[] =
|
||||
"package a;\n"
|
||||
"export def J: i32 = 41;\n"
|
||||
"package b;\n"
|
||||
"import a;\n"
|
||||
"def K: i32 = a.J + 1;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet k: i32 = K;\n"
|
||||
"\tif (k == 42) { return 42; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n";
|
||||
|
||||
/* Assert the folded DATA row for symbol `b.K` is present with value 42
|
||||
* (first byte '*'). Returns 0 on success. */
|
||||
static int
|
||||
xmod_fold_present(const char *w6c, const char *stage)
|
||||
{
|
||||
char src[64], s[64], buf[1 << 15];
|
||||
snprintf(src, sizeof src, "/tmp/dcf_xm_%d_%s.ww", getpid(), stage);
|
||||
snprintf(s, sizeof s, "/tmp/dcf_xm_%d_%s.s", getpid(), stage);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(xmod_src, f);
|
||||
fclose(f);
|
||||
|
||||
if (emit_s(w6c, src, s, sizeof s) != 0) {
|
||||
fprintf(stderr, "xmod[%s]: w6c emit failed\n", stage);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
int rc = 0;
|
||||
if (slurp(s, buf, sizeof buf) < 0) {
|
||||
rc = -1;
|
||||
} else if (strstr(buf, "b.K(SB),\"*") == NULL) {
|
||||
fprintf(stderr,
|
||||
"xmod[%s]: no folded `DATA b.K(SB),\"*` (value 42) row\n",
|
||||
stage);
|
||||
rc = -1;
|
||||
}
|
||||
unlink(src); unlink(s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---- 4. fail-loud rows: must fail the build on BOTH stages ---------- */
|
||||
|
||||
struct failrow { const char *label; const char *src; };
|
||||
|
||||
static const struct failrow fail_rows[] = {
|
||||
/* Same-module cycle: the depth guard must trip and fail loud, not
|
||||
* hang and not silently drop the row. */
|
||||
{ "same-module-cycle",
|
||||
"def A: i32 = B;\n"
|
||||
"def B: i32 = A;\n"
|
||||
"export fn main() i32 = { return A; };\n" },
|
||||
|
||||
/* Cross-module cycle (combined source): a.J -> b.K -> a.J. */
|
||||
{ "cross-module-cycle",
|
||||
"package a;\n"
|
||||
"import b;\n"
|
||||
"export def J: i32 = b.K;\n"
|
||||
"package b;\n"
|
||||
"import a;\n"
|
||||
"export def K: i32 = a.J;\n"
|
||||
"export fn main() i32 = { return 0; };\n" },
|
||||
|
||||
/* Narrowing cast that loses the value: 0x1FF (511) does not fit a
|
||||
* u8, so the cast must fail loud rather than silently truncate. */
|
||||
{ "narrowing-cast",
|
||||
"def Z: u8 = 0x1FF: u8;\n"
|
||||
"export fn main() i32 = { let z: u8 = Z; return z: i32; };\n" },
|
||||
};
|
||||
|
||||
/* Compile via w6c only (no link/run); success means the build FAILED as
|
||||
* required (nonzero w6c exit). */
|
||||
static int
|
||||
compile_fails(const char *w6c, const struct failrow *r, const char *stage, int i)
|
||||
{
|
||||
char src[64], s[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/dcf_fl_%d_%d.ww", getpid(), i);
|
||||
snprintf(s, sizeof s, "/tmp/dcf_fl_%d_%d.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
/* A regressed depth guard would HANG the cycle rows rather than fail
|
||||
* loud (rule 7). timeout's 124 is distinct from a clean guard failure
|
||||
* -- fold it back to a TEST failure so a hang can't pass as fail-loud. */
|
||||
snprintf(cmd, sizeof cmd, "timeout 180 %s -o %s %s 2>/dev/null",
|
||||
w6c, s, src);
|
||||
int rc = runwait(cmd);
|
||||
unlink(src); unlink(s);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr,
|
||||
"failrow[%s][%s]: w6c exited 0 (expected loud failure)\n",
|
||||
r->label, stage);
|
||||
return -1;
|
||||
}
|
||||
if (rc == 124) {
|
||||
fprintf(stderr,
|
||||
"failrow[%s][%s]: w6c timed out (depth guard hung?)\n",
|
||||
r->label, stage);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024], w6c[1024], w6c_ww[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
int have_ww = (access(w6c_ww, X_OK) == 0);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
/* 1. same-module exec via cstage `ww` (+ wwstage `ww_ww` if built) */
|
||||
struct { const char *name; const char *path; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
int nexec = (int)(sizeof exec_rows / sizeof exec_rows[0]);
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "def_const_fold: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nexec; i++) {
|
||||
int got = run_driver(drivers[d].path, &exec_rows[i], i);
|
||||
total++;
|
||||
if (got != exec_rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"def_const_fold[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, exec_rows[i].label,
|
||||
got, exec_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. byte-id on every exec row (only when wwstage is built) */
|
||||
if (have_ww) {
|
||||
for (int i = 0; i < nexec; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, exec_rows[i].src,
|
||||
exec_rows[i].label, i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. cross-module fold: DATA row value on cstage, + byte-id */
|
||||
total++;
|
||||
if (xmod_fold_present(w6c, "cstage") != 0) fail++;
|
||||
if (have_ww) {
|
||||
total++;
|
||||
if (xmod_fold_present(w6c_ww, "wwstage") != 0) fail++;
|
||||
total++;
|
||||
if (asm_byte_identical(bin, xmod_src, "xmod-fold", 900) != 0)
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* 4. fail-loud on BOTH stages */
|
||||
int nfail = (int)(sizeof fail_rows / sizeof fail_rows[0]);
|
||||
for (int i = 0; i < nfail; i++) {
|
||||
total++;
|
||||
if (compile_fails(w6c, &fail_rows[i], "cstage", i) != 0) fail++;
|
||||
if (have_ww) {
|
||||
total++;
|
||||
if (compile_fails(w6c_ww, &fail_rows[i], "wwstage",
|
||||
1000 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"def_const_fold: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("def_const_fold: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user