w6c+selfhost: principled sub-word signedness (closes #5/#10)

type_isunsigned recurses TY_ENUM and includes TY_RUNE on both stages.
13 LOAD + 6 STORE ladder sites (cstage) plus 4 more wwstage stragglers
in cgindex/cgforrange collapsed to fldloadop/fldstoreop helpers. N_CAST
narrow gate symmetrised; task #1's literal-kind workaround retired.
bool kept out of type_isunsigned, special-cased in field helpers.

Retroactively fixes a u32 mis-sign-extend in deref-compound (sz=4
hardcoded MOVSXD), pinned by new 660_field_signed row.
This commit is contained in:
2026-05-13 22:23:00 +09:00
parent 461a448d5f
commit c4b3aca5e4
9 changed files with 976 additions and 535 deletions

View File

@@ -131,6 +131,43 @@ fld_isfloat(Type *t, int *isf32)
return 0;
}
/* fld_issigned — true iff a sub-word field/element load needs sign
* extension (i8 → MOVSBQ, i16 → MOVSWQ, i32 → MOVSXD). Follows NAMED
* and ENUM aliases via type_isunsigned, then peels off the unsigned
* cases (u*, bool, rune) so what remains is the genuinely-signed
* narrow integers. The literal-kind ladder this replaces missed
* TY_ENUM aliases entirely (`type myflag = i8` silently emitted
* MOVZBQ on a field load). */
static int
fld_issigned(Type *t)
{
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
if (u == NULL) return 0;
if (u->kind == TY_BOOL) return 0;
if (type_isunsigned(u)) return 0;
return type_isint(u);
}
static int
fldloadop(Type *t, int sz)
{
int sigd = fld_issigned(t);
if (sz == 1) return sigd ? A_MOVSBQ : A_MOVZBQ;
if (sz == 2) return sigd ? A_MOVSWQ : A_MOVZWQ;
if (sz == 4) return sigd ? A_MOVSXD : A_MOVL;
return A_MOVQ;
}
static int
fldstoreop(Type *t, int sz)
{
(void)t;
if (sz == 1) return A_MOVB;
if (sz == 2) return A_MOVW;
if (sz == 4) return A_MOVL;
return A_MOVQ;
}
/* struct ≤16B all-INTEGER: 1 or 2 eightbyte regs.
* Returns 0 if not a struct or too large. */
static int
@@ -1720,13 +1757,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
int fsz = (int)(f->type ? f->type->size : 8);
int signed_field = f->type && (
f->type->kind == TY_I8 ||
f->type->kind == TY_I16 ||
f->type->kind == TY_I32);
int load_op = A_MOVQ, store_op = A_MOVQ;
if (fsz == 1) { load_op = A_MOVZBQ; store_op = A_MOVB; }
else if (fsz == 4) { load_op = signed_field ? A_MOVSXD : A_MOVL; store_op = A_MOVL; }
int load_op = fldloadop(f->type, fsz);
int store_op = fldstoreop(f->type, fsz);
int boff = localfind(locals, base->str);
int is_global = (boff == 0 && !via_ptr
&& let_islet(base->str));
@@ -1840,13 +1872,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
int fsz = (int)(ft ? ft->size : 8);
int signed_field = ft && (
ft->kind == TY_I8 ||
ft->kind == TY_I16 ||
ft->kind == TY_I32);
int store_op = A_MOVQ;
if (fsz == 1) store_op = A_MOVB;
else if (fsz == 4) store_op = A_MOVL;
int store_op = fldstoreop(ft, fsz);
int foff = (int)f->offset;
if (n->op == TK_ASSIGN) {
int c_isf32 = 0;
@@ -1907,10 +1933,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins1(c, A_PUSHQ, areg(D_AX));
cgexpr(c, n->lhs->lhs, locals);
ins1(c, A_PUSHQ, areg(D_AX));
int load_op = A_MOVQ;
if (fsz == 1) load_op = A_MOVZBQ;
else if (fsz == 4)
load_op = signed_field ? A_MOVSXD : A_MOVL;
int load_op = fldloadop(ft, fsz);
ins2(c, load_op, amem(D_AX, foff),
areg(D_AX));
ins1(c, A_POPQ, areg(D_BX));
@@ -2037,9 +2060,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
? leaf_type->under : leaf_type;
int fsz = (int)(leaf_type
? leaf_type->size : 8);
int store_op = A_MOVQ;
if (fsz == 1) store_op = A_MOVB;
else if (fsz == 4) store_op = A_MOVL;
int store_op = fldstoreop(leaf_type, fsz);
if (fu && fu->kind == TY_STR) {
cgexpr(c, n->rhs, locals);
if (is_global) {
@@ -2268,10 +2289,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 8));
break;
}
int store_op = A_MOVQ;
if (esz == 1) store_op = A_MOVB;
else if (esz == 2) store_op = A_MOVW;
else if (esz == 4) store_op = A_MOVL;
int store_op = fldstoreop(esub, esz);
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
break;
}
@@ -2331,12 +2349,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
} else {
ins1(c, A_POPQ, areg(D_AX));
int sz = vt ? (int)vt->size : 8;
int signed_field = vt && (vt->kind == TY_I8 ||
vt->kind == TY_I16 || vt->kind == TY_I32);
(void)signed_field;
int store_op = A_MOVQ;
if (sz == 1) store_op = A_MOVB;
else if (sz == 4) store_op = A_MOVL;
int store_op = fldstoreop(vt, sz);
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
}
break;
@@ -2355,17 +2368,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL;
if (vt && vt->kind == TY_NAMED) vt = vt->under;
int sz = vt ? (int)vt->size : 8;
int load_op = A_MOVQ, store_op = A_MOVQ;
int handled = 1;
if (sz == 8) {
load_op = A_MOVQ; store_op = A_MOVQ;
} else if (sz == 4) {
load_op = A_MOVSXD; store_op = A_MOVL;
} else if (sz == 1) {
load_op = A_MOVZBQ; store_op = A_MOVB;
} else {
handled = 0;
}
int load_op = fldloadop(vt, sz);
int store_op = fldstoreop(vt, sz);
int handled = (sz == 1 || sz == 2 || sz == 4 || sz == 8);
if (handled) {
cgexpr(c, n->rhs, locals); /* AX = rhs */
ins1(c, A_PUSHQ, areg(D_AX));
@@ -2725,14 +2730,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *st = sn->type;
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
int esz = (su && su->sub) ? (int)su->sub->size : 1;
Type *esub = su ? su->sub : NULL;
int sn_off = (sn->kind == N_IDENT)
? localfind(locals, sn->str) : 0;
int store_op = (esz == 1) ? A_MOVB : A_MOVQ;
int store_op = fldstoreop(esub, esz);
for (Node *vn = sn->next; vn; vn = vn->next) {
if (vn->kind == N_SPREAD &&
vn->lhs && vn->lhs->kind == N_IDENT) {
int it_off = localfind(locals, vn->lhs->str);
int load_op = (esz == 1) ? A_MOVZBQ : A_MOVQ;
int load_op = fldloadop(esub, esz);
/* push counter (i) on stack */
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
ins2(c, A_MOVQ, aimm(0), amem(D_SP, 0));
@@ -3641,36 +3647,41 @@ cgexpr(Cg *c, Node *n, Local *locals)
* 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. */
* -128i64 after a widening read-back. Symmetric on signed
* vs unsigned: both branches gate on `type_isint(tu) &&
* size<8`, then dispatch on type_isunsigned(tu). The
* recursion through TY_ENUM in type_isunsigned (task #5)
* is what lets an enum-aliased narrow (`type myflag = i8`)
* pick up the right MOVS*Q. Wwstage's cgcast keys off the
* resolved type-name through the same shape. TY_RUNE is
* unsigned (Unicode scalar) and lands on the MOVL path. */
if (!from_f && !to_f && n->type) {
Type *tt = n->type;
Type *tu = (tt && tt->kind == TY_NAMED) ? tt->under : tt;
if (tu && type_isint(tu) && tu->size > 0
&& tu->size < 8 && type_isunsigned(tu)) {
if (tu->size == 4) {
ins2(c, A_MOVL, areg(D_AX), areg(D_AX));
&& tu->size < 8) {
if (type_isunsigned(tu)) {
if (tu->size == 4) {
ins2(c, A_MOVL,
areg(D_AX), areg(D_AX));
} else {
u64 mask = ((u64)1 << (tu->size * 8)) - 1;
ins2(c, A_ANDQ,
aimm((i64)mask),
areg(D_AX));
}
} else {
u64 mask = ((u64)1 << (tu->size * 8)) - 1;
ins2(c, A_ANDQ, aimm((i64)mask),
areg(D_AX));
int op = A_MOVSXD;
if (tu->size == 1) op = A_MOVSBQ;
else if (tu->size == 2) op = A_MOVSWQ;
ins2(c, op, areg(D_AX), 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
* instead of leaking the upper bits. */
* instead of leaking the upper bits. type_isint(bool)
* is false, so the symmetric narrow above misses it
* — this dedicated branch covers the bool case. */
if (tu && tu->kind == TY_BOOL) {
ins2(c, A_ANDQ, aimm(0xFF), areg(D_AX));
}
@@ -3822,14 +3833,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
int fsz = (int)(leaf_type
? leaf_type->size : 8);
int signed_field = leaf_type && (
leaf_type->kind == TY_I8
|| leaf_type->kind == TY_I16
|| leaf_type->kind == TY_I32);
int op = A_MOVQ;
if (fsz == 1) op = A_MOVZBQ;
else if (fsz == 4)
op = signed_field ? A_MOVSXD : A_MOVL;
int op = fldloadop(leaf_type, fsz);
ins2(c, op,
amem(base_reg,
base_disp + total_off),
@@ -3924,13 +3928,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
int fsz = (int)(tp->type ? tp->type->size : 8);
Type *fu = (tp->type && tp->type->kind == TY_NAMED)
? tp->type->under : tp->type;
int signed_field = tp->type && (
tp->type->kind == TY_I8 ||
tp->type->kind == TY_I16 ||
tp->type->kind == TY_I32);
int op = A_MOVQ;
if (fsz == 1) op = A_MOVZBQ;
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
int op = fldloadop(tp->type, fsz);
int off = localfind(locals, n->lhs->str);
/* str element: load (ptr, len) into (AX, BX) so chains
* like `t.1.len` propagate through the str-rhs
@@ -4009,13 +4007,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
int fsz = (int)(f->type ? f->type->size : 8);
int signed_field = f->type && (
f->type->kind == TY_I8 ||
f->type->kind == TY_I16 ||
f->type->kind == TY_I32);
int op = A_MOVQ;
if (fsz == 1) op = A_MOVZBQ;
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
int op = fldloadop(f->type, fsz);
ins2(c, op,
amem(base_reg, base_disp + (int)f->offset),
areg(D_AX));
@@ -4077,13 +4069,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
int fsz = (int)(f->type ? f->type->size : 8);
int signed_field = f->type && (
f->type->kind == TY_I8 ||
f->type->kind == TY_I16 ||
f->type->kind == TY_I32);
int op = A_MOVQ;
if (fsz == 1) op = A_MOVZBQ;
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
int op = fldloadop(f->type, fsz);
ins2(c, op,
amem(D_BX, (int)f->offset),
areg(D_AX));
@@ -4131,13 +4117,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
goto dot_done;
}
int fsz = (int)(ft ? ft->size : 8);
int signed_field = ft && (
ft->kind == TY_I8 ||
ft->kind == TY_I16 ||
ft->kind == TY_I32);
int op = A_MOVQ;
if (fsz == 1) op = A_MOVZBQ;
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
int op = fldloadop(ft, fsz);
ins2(c, op, amem(D_AX, (int)f->offset),
areg(D_AX));
goto dot_done;
@@ -4230,13 +4210,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_AX));
break;
}
int signed_elem = esub && (
esub->kind == TY_I8 || esub->kind == TY_I16 ||
esub->kind == TY_I32);
int load_op = A_MOVQ;
if (esz == 1) load_op = A_MOVZBQ;
else if (esz == 2) load_op = signed_elem ? A_MOVSWQ : A_MOVZWQ;
else if (esz == 4) load_op = signed_elem ? A_MOVSXD : A_MOVL;
int load_op = fldloadop(esub, esz);
ins2(c, load_op, amem(D_BX, 0), areg(D_AX));
break;
}
@@ -4278,14 +4252,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
{
int signed_elem = esub && (
esub->kind == TY_I8 || esub->kind == TY_I16 ||
esub->kind == TY_I32);
int load_op = A_MOVQ;
if (esz == 1) load_op = A_MOVZBQ;
else if (esz == 2) load_op = signed_elem ? A_MOVSWQ : A_MOVZWQ;
else if (esz == 4)
load_op = signed_elem ? A_MOVSXD : A_MOVL;
int load_op = fldloadop(esub, esz);
ins2(c, load_op, amem(D_AX, 0), areg(D_AX));
}
break;
@@ -4883,7 +4850,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
int loff = localoff(c, locals, lname, 8, frame);
/* allocate per-name slots */
struct { int off, sz, foff; int signed_field; } binds[8] = {0};
struct { int off, sz, foff; Type *ftype; } binds[8] = {0};
int nbinds = 0;
if (destruct) {
Tparam *tp = (etu && etu->kind == TY_TUPLE) ?
@@ -4894,10 +4861,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
int slot_sz = (fsz < 8) ? 8 : fsz;
binds[nbinds].sz = fsz;
binds[nbinds].foff = field_off;
binds[nbinds].signed_field = tp && tp->type && (
tp->type->kind == TY_I8 ||
tp->type->kind == TY_I16 ||
tp->type->kind == TY_I32);
binds[nbinds].ftype = tp ? tp->type : NULL;
binds[nbinds].off = localoff(c, locals,
nm->str, slot_sz, frame);
field_off += fsz;
@@ -4909,10 +4873,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
binds[0].off = localoff(c, locals, n->str, slot_sz, frame);
binds[0].sz = esz;
binds[0].foff = 0;
binds[0].signed_field = u && u->sub && (
u->sub->kind == TY_I8 ||
u->sub->kind == TY_I16 ||
u->sub->kind == TY_I32);
binds[0].ftype = u ? u->sub : NULL;
nbinds = 1;
}
@@ -4959,10 +4920,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
/* load each binding from BX + foff into its slot */
for (int b = 0; b < nbinds; b++) {
int op = A_MOVQ;
if (binds[b].sz == 1) op = A_MOVZBQ;
else if (binds[b].sz == 4)
op = binds[b].signed_field ? A_MOVSXD : A_MOVL;
int op = fldloadop(binds[b].ftype, binds[b].sz);
ins2(c, op, amem(D_BX, binds[b].foff), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, binds[b].off));
}

View File

@@ -170,8 +170,10 @@ type_isunsigned(Type *t)
switch (t->kind) {
case TY_U8: case TY_U16: case TY_U32: case TY_U64:
case TY_UINT: case TY_UINTPTR:
case TY_RUNE:
return 1;
case TY_NAMED: return type_isunsigned(t->under);
case TY_NAMED: return type_isunsigned(t->under);
case TY_ENUM: return type_isunsigned(t->sub);
default: return 0;
}
}