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

@@ -499,11 +499,39 @@ a_encode(Asm *a)
a_emit_byte(a, 0xBF);
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
a_emit_byte(a, 0x0F);
a_emit_byte(a, 0xBF);
a_emit_byte(a, modrm(3,
rcode(p->to.type), rcode(p->from.type)));
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVSWQ shape\n", p->line);
a->errs++;
}
break;
case A_MOVSBQ:
/* MOVSX r64, r/m8 — 0F BE /r with REX.W */
if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
a_emit_byte(a, 0x0F);
a_emit_byte(a, 0xBE);
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
a_emit_byte(a, 0x0F);
a_emit_byte(a, 0xBE);
a_emit_byte(a, modrm(3,
rcode(p->to.type), rcode(p->from.type)));
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVSBQ shape\n", p->line);
a->errs++;
}
break;
case A_MOVB:
/* MOV r/m8, r8 — 88 /r. No REX.W. We always emit REX
* to allow access to SIL/DIL/BPL/SPL. */
@@ -574,6 +602,12 @@ a_encode(Asm *a)
a_emit_byte(a, 0x63);
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
a_emit_byte(a, 0x63);
a_emit_byte(a, modrm(3,
rcode(p->to.type), rcode(p->from.type)));
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVSXD shape\n", p->line);
a->errs++;

View File

@@ -86,6 +86,7 @@ opcode_lookup(const char *m)
{ "MOVW", A_MOVW }, { "MOVB", A_MOVB },
{ "MOVZBQ", A_MOVZBQ }, { "MOVZWQ", A_MOVZWQ },
{ "MOVSXD", A_MOVSXD }, { "MOVSWQ", A_MOVSWQ },
{ "MOVSBQ", A_MOVSBQ },
{ "MOVSD", A_MOVSD },
{ "ADDSD", A_ADDSD },{ "SUBSD", A_SUBSD },
{ "MULSD", A_MULSD },{ "DIVSD", A_DIVSD },

View File

@@ -51,10 +51,11 @@ enum {
A_MOVL,
A_MOVW,
A_MOVB,
A_MOVZBQ, /* movzx r64, r/m8 — load byte zero-extended */
A_MOVZWQ, /* movzx r64, r/m16 — load word zero-extended */
A_MOVSXD, /* movsxd r64, r/m32 — load i32 sign-extended */
A_MOVSWQ, /* movsx r64, r/m16 — load word sign-extended */
A_MOVZBQ, /* movzx r64, r/m8 — byte zero-extended */
A_MOVZWQ, /* movzx r64, r/m16 — word zero-extended */
A_MOVSXD, /* movsxd r64, r/m32 — i32 sign-extended */
A_MOVSWQ, /* movsx r64, r/m16 — i16 sign-extended */
A_MOVSBQ, /* movsx r64, r/m8 — i8 sign-extended */
/* SSE2 scalar double-precision float */
A_MOVSD, /* xmm/m → xmm and xmm → m */

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

View File

@@ -37,6 +37,7 @@ anames(int op)
case A_MOVZWQ: return "MOVZWQ";
case A_MOVSXD: return "MOVSXD";
case A_MOVSWQ: return "MOVSWQ";
case A_MOVSBQ: return "MOVSBQ";
case A_MOVSD: return "MOVSD";
case A_ADDSD: return "ADDSD";
case A_SUBSD: return "SUBSD";