w6c+selfhost: cgen N_CAST signed narrow + peel !T on unsigned narrow

TY_RUNE excluded — wwstage primsize skips it; task #5 will collapse
both gates back to symmetric once type_isunsigned recurses TY_ENUM.
This commit is contained in:
2026-05-13 20:06:24 +09:00
parent 777c951fab
commit 388ab8a707
14 changed files with 421 additions and 32 deletions

View File

@@ -3490,13 +3490,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
* width (mod 2^n). Without this, `(big_u64): u32` left the
* upper 32 bits intact and CMPQ/DIVQ misread the value.
*
* Unsigned targets only here. Signed-narrow targets (i8/
* i16/i32) need MOVSBQ / MOVSWQ / MOVSXD in their reg-reg
* form which the assembler doesn't expose yet; callers
* that need a clean signed-narrow value either keep the
* value in range before the cast (as strconv does with
* an explicit bounds check) or AND the low bits manually.
* Tracking this gap is part of the same TODO. */
* Unsigned targets use MOVL/ANDQ to clear the high bits.
* Signed-narrow targets (i8/i16/i32) sign-extend via
* MOVSBQ/MOVSWQ/MOVSXD reg-reg so the sign bit propagates;
* this is what lets `(0xFF80i64): i8` compare equal to
* -128i64 after a widening read-back. Gated on the literal
* TY_I8/TY_I16/TY_I32 kinds so the wwstage cgen path
* (cgcast in cgenexpr.ww, which keys off primsize on the
* type-name node) emits the same instructions for the same
* inputs — that byte-identity gate is what test 993 pins. */
if (!from_f && !to_f && n->type) {
Type *tt = n->type;
Type *tu = (tt && tt->kind == TY_NAMED) ? tt->under : tt;
@@ -3509,6 +3511,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_ANDQ, aimm((i64)mask),
areg(D_AX));
}
} else if (tu && (tu->kind == TY_I8
|| tu->kind == TY_I16 || tu->kind == TY_I32)) {
/* Literal-kind gate excludes TY_RUNE on purpose:
* runes are unsigned Unicode scalars, owed to the
* MOVL path once task #5 lands. */
int op = A_MOVSXD;
if (tu->kind == TY_I8) op = A_MOVSBQ;
else if (tu->kind == TY_I16) op = A_MOVSWQ;
ins2(c, op, areg(D_AX), areg(D_AX));
}
/* TY_BOOL is size 1 too; clamp to a single byte so
* `(u32_val): bool` produces 0 or a low-byte value