cstage+selfhost+test: principled identity-cast skip (#33)
Generalizes b5632b1's single-site dst_is_enum gate. Skip the narrow- clamp MOVL when src.width == dst.width && src.signed == dst.signed. Closes #25's followup. Both stages need symmetric source-type derivation for byte-id. cstage deliberately throws away the checker's richer typed-AST and uses a structural walker (castsrcprim) that mirrors wwstage's exprprimresolved case-for-case. Otherwise cstage's `.len: i32` resolves to i32 (skip) while wwstage's misses the pseudo-field (clamp) — bootstrap diverges. Pseudo-fields, N_BIN, N_INDEX, N_CALL, match-bindings all yield sz=0 → clamp emits defensively on both. The N_TENUM walker now follows enum aliases in wwstage's typenodeprimresolved (was the original lacuna behind #25), and bool is excluded early in the same helper (mirrors cstage's type_isint(TY_BOOL)=false). bool→bool keeps its dedicated is_bool ANDQ $255 emit; bool→i8 / bool→u8 etc. fall through to the clamp on both stages. Walker shape (cstage castsrcprim / wwstage exprprimresolved): N_INTLIT → tsuffix gated, untyped excluded N_IDENT → trust local's resolved tnode N_CAST → recurse on declared dst N_UN → recurse on operand N_DOT → real-struct only (TY_STRUCT or TY_PTR→TY_STRUCT) others → sz=0 → identity false → clamp emits Test 710 grew from 5 → 16 rows: 6 identity-width pins (u32/i32/u8/ i8/u16/i16 self), 1 sign-change pin (u32→i32 clamp MUST fire), 2 silent-miscompile exit-validating rows (truncate via divide), 1 pseudo-field defensive pin (`s.len: i32`), 1 bool-source pin (`b: i8`). Asm byte-id asserted on every row. Out of scope: redundant clamps remain for patterns wwstage can't structurally derive (N_BIN, N_CALL, N_INDEX, pseudo-fields). A sibling task extending wwstage's type inference closes those.
This commit is contained in:
@@ -381,55 +381,79 @@ fn cgcast(c: *cgen, n: *node) void = {
|
||||
// Mirrors cmd/w6c/cgen.c's N_CAST clamp. Unsigned narrow clears
|
||||
// the upper bits via MOVL/ANDQ; signed narrow sign-extends via
|
||||
// MOVSBQ/MOVSWQ/MOVSXD reg-reg so the sign bit propagates.
|
||||
//
|
||||
// Identity-width identity-sign cast is a no-op at the machine-
|
||||
// int level: src and dst share both width and signedness, so the
|
||||
// natural slot/load already carries the right canonical 64-bit
|
||||
// shape. Skip the clamp in that case. Symmetric with cstage's
|
||||
// principled gate (#33). Replaces the previous N_TENUM lacuna in
|
||||
// this walker (the alias-step missed `N_TENUM`, so any cast to
|
||||
// an enum dst landed on tn==nil and skipped the clamp by
|
||||
// accident — task #25 mirrored that into cstage as a single-site
|
||||
// gate, and #33 retires both). The walker now follows N_TENUM
|
||||
// too so a narrow-to-enum cast (u32→enum-u8, i64→enum-i32)
|
||||
// resolves to the underlying primitive and the clamp fires —
|
||||
// fixing a silent miscompile in the process.
|
||||
if (srcfk == 0 && dstfk == 0) {
|
||||
let tn: *node = n.rhs;
|
||||
// Walk through alias chains (`type random = u64`) and the
|
||||
// `!T` error-flag wrapper (`type invalid = !i32`) — the
|
||||
// bang is a tagged-union marker, not a representational
|
||||
// change, so it must not block the narrow-cast clamp.
|
||||
for (tn != nil) {
|
||||
if (tn.kind == nkind.N_TBANG) { tn = tn.lhs; }
|
||||
else { if (tn.kind != nkind.N_TNAME) { tn = nil; }
|
||||
else {
|
||||
let nm: str = tn.str;
|
||||
if (primsize(nm) > 0) { break; };
|
||||
let alias: *node = aliaslookup(c, nm);
|
||||
if (alias == nil) { tn = nil; }
|
||||
else { tn = alias; };
|
||||
}; };
|
||||
let sz: i32 = 0;
|
||||
let is_unsigned: bool = false;
|
||||
typenodeprimresolved(c, n.rhs, &sz, &is_unsigned);
|
||||
let src_sz: i32 = 0;
|
||||
let src_unsigned: bool = false;
|
||||
exprprimresolved(c, n.lhs, &src_sz, &src_unsigned);
|
||||
let identity: bool = false;
|
||||
if (sz > 0) { if (src_sz == sz) {
|
||||
if (src_unsigned == is_unsigned) { identity = true; };
|
||||
}; };
|
||||
// Detect bool dst by walking n.rhs to the leaf TNAME. bool
|
||||
// keeps its dedicated ANDQ $255 contract regardless of
|
||||
// upstream shape; it stays off the identity path.
|
||||
let leaf_tn: *node = n.rhs;
|
||||
for (leaf_tn != nil) {
|
||||
let lk: nkind = leaf_tn.kind;
|
||||
if (lk == nkind.N_TBANG) { leaf_tn = leaf_tn.lhs; }
|
||||
else { if (lk == nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; }
|
||||
else { if (lk == nkind.N_TNAME) {
|
||||
let lnm: str = leaf_tn.str;
|
||||
if (primsize(lnm) > 0) { break; };
|
||||
let lal: *node = aliaslookup(c, lnm);
|
||||
if (lal == nil) { leaf_tn = nil; }
|
||||
else { leaf_tn = lal; };
|
||||
}
|
||||
else { leaf_tn = nil; }; }; };
|
||||
};
|
||||
if (tn != nil) {
|
||||
let nm: str = tn.str;
|
||||
let sz: i32 = primsize(nm);
|
||||
let is_unsigned: bool = typenameisunsigned(nm);
|
||||
let is_bool: bool = streq(nm, "bool");
|
||||
// Symmetric narrow on signed vs unsigned (task #5):
|
||||
// unsigned (incl. rune) clears upper bits; signed
|
||||
// sign-extends. bool is size 1 but neither — falls
|
||||
// through to its dedicated ANDQ $255 below.
|
||||
if (sz > 0) { if (sz < 8) { if (!is_bool) {
|
||||
if (is_unsigned) {
|
||||
if (sz == 4) {
|
||||
emitline("\tMOVL\tAX, AX\n");
|
||||
} else {
|
||||
let mask: i64 = 0xFFi64;
|
||||
if (sz == 2) { mask = 0xFFFFi64; };
|
||||
emitline("\tANDQ\t$");
|
||||
emitint(mask);
|
||||
emitline(", AX\n");
|
||||
};
|
||||
let is_bool: bool = false;
|
||||
if (leaf_tn != nil) {
|
||||
if (leaf_tn.kind == nkind.N_TNAME) {
|
||||
is_bool = streq(leaf_tn.str, "bool");
|
||||
};
|
||||
};
|
||||
// Symmetric narrow on signed vs unsigned (task #5):
|
||||
// unsigned (incl. rune) clears upper bits; signed
|
||||
// sign-extends. bool is size 1 but neither — falls
|
||||
// through to its dedicated ANDQ $255 below.
|
||||
if (sz > 0) { if (sz < 8) { if (!is_bool) { if (!identity) {
|
||||
if (is_unsigned) {
|
||||
if (sz == 4) {
|
||||
emitline("\tMOVL\tAX, AX\n");
|
||||
} else {
|
||||
if (sz == 1) {
|
||||
emitline("\tMOVSBQ\tAX, AX\n");
|
||||
} else { if (sz == 2) {
|
||||
emitline("\tMOVSWQ\tAX, AX\n");
|
||||
} else { if (sz == 4) {
|
||||
emitline("\tMOVSXD\tAX, AX\n");
|
||||
}; }; };
|
||||
let mask: i64 = 0xFFi64;
|
||||
if (sz == 2) { mask = 0xFFFFi64; };
|
||||
emitline("\tANDQ\t$");
|
||||
emitint(mask);
|
||||
emitline(", AX\n");
|
||||
};
|
||||
}; }; };
|
||||
if (is_bool) { emitline("\tANDQ\t$255, AX\n"); };
|
||||
};
|
||||
} else {
|
||||
if (sz == 1) {
|
||||
emitline("\tMOVSBQ\tAX, AX\n");
|
||||
} else { if (sz == 2) {
|
||||
emitline("\tMOVSWQ\tAX, AX\n");
|
||||
} else { if (sz == 4) {
|
||||
emitline("\tMOVSXD\tAX, AX\n");
|
||||
}; }; };
|
||||
};
|
||||
}; }; }; };
|
||||
if (is_bool) { emitline("\tANDQ\t$255, AX\n"); };
|
||||
return;
|
||||
};
|
||||
if (srcfk == 0 && dstfk == 2) {
|
||||
|
||||
Reference in New Issue
Block a user