diff --git a/Makefile b/Makefile index b2b842d3..e67d7668 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \ $(BIN)/test_at_test $(BIN)/test_let_global \ $(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \ + $(BIN)/test_field_signed \ $(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \ $(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \ $(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww @@ -289,6 +290,12 @@ $(BIN)/test_dot_chain: test/wcc/650_dot_chain.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_field_signed: test/wcc/660_field_signed.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_selfhost: test/wcc/990_selfhost.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww $(BIN)/wwdump $(BIN)/wwdump_ww $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index f8f2c74e..1430317d 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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)); } diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index dc165922..5d0e2063 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -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; } } diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 4011334d..ebf254b7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6072,7 +6072,10 @@ fn nodeisstr(c: *cgen, n: *node) bool = { return false; }; -// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr. +// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr/rune. +// rune is a Unicode codepoint (0..0x10FFFF); cgen treats it as +// unsigned so narrow-cast / sub-word load paths zero-extend (MOVL, +// not MOVSXD). Mirrors cstage's type_isunsigned post task #5. fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; @@ -6080,10 +6083,39 @@ fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u64")) { return true; }; if (streq(nm, "uint")) { return true; }; if (streq(nm, "uintptr")) { return true; }; + if (streq(nm, "rune")) { return true; }; return false; }; -// typenodeisunsigned — recurse through TNAME / TPTR / TSLICE etc. +// typenodeisunsigned — recurse through TNAME aliases / TBANG / TENUM +// to the resolved primitive. Mirrors cstage's type_isunsigned which +// recurses into TY_NAMED.under and TY_ENUM.sub. +fn typenodeisunsignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + if (k == nkind.N_TBANG) { return typenodeisunsignedc(c, t.lhs); }; + if (k == nkind.N_TENUM) { return typenodeisunsignedc(c, t.lhs); }; + if (k == nkind.N_TNAME) { + let nm: str = t.str; + if (typenameisunsigned(nm)) { return true; }; + if (typenameissigned(nm)) { return false; }; + // Follow aliases / enum storage. + let al: *node = aliaslookup(c, nm); + if (al != nil) { return typenodeisunsignedc(c, al); }; + let en: *enumtype = enumlookup(c, nm); + if (en != nil) { + if (en.storage != nil) { + return typenodeisunsignedc(c, en.storage); + }; + return false; // default storage i32 is signed + }; + }; + return false; +}; + +// typenodeisunsigned — legacy callers without *cgen context. Only +// resolves primitive TNAMEs (no alias/enum recursion); use the +// _c variant where the cgen registry is in scope. fn typenodeisunsigned(t: *node) bool = { if (t == nil) { return false; }; if (t.kind == nkind.N_TNAME) { return typenameisunsigned(t.str); }; @@ -6138,9 +6170,21 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { }; // elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is -// its element a signed narrow primitive (i8/i16/i32/rune)? Used by -// cgindex to pick MOVSXD vs MOVL at esz=4. Mirrors C cgen's -// `signed_elem` check. +// its element a signed narrow primitive (i8/i16/i32)? Used by +// cgindex to pick MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at +// esz=1/2). Mirrors cstage's `signed_elem`. Follows alias/enum +// chains so `[]Alias` arrays resolve to the underlying signedness. +fn elemissignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let elem: *node = nil; + let k: nkind = t.kind; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + return fieldissignedc(c, elem); +}; + fn elemissigned(t: *node) bool = { if (t == nil) { return false; }; let elem: *node = nil; @@ -6153,47 +6197,103 @@ fn elemissigned(t: *node) bool = { return typenameissigned(elem.str); }; -// typenameissigned — true for i8/i16/i32/i64/int/rune. +// typenameissigned — true for i8/i16/i32/i64/int. rune is excluded +// (it's a non-negative Unicode codepoint, treated as unsigned). fn typenameissigned(nm: str) bool = { if (streq(nm, "i8")) { return true; }; if (streq(nm, "i16")) { return true; }; if (streq(nm, "i32")) { return true; }; if (streq(nm, "i64")) { return true; }; if (streq(nm, "int")) { return true; }; - if (streq(nm, "rune")) { return true; }; + return false; +}; + +// fieldissignedc — does this field/element type need sign-extension +// on a sub-word load? Walks TBANG / TENUM / TNAME-aliases to the +// resolved primitive. Mirrors cstage's fld_issigned: bool is treated +// as unsigned (0/1 ⇒ MOVZBQ); rune is unsigned (codepoint ⇒ MOVL). +fn fieldissignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + if (k == nkind.N_TBANG) { return fieldissignedc(c, t.lhs); }; + if (k == nkind.N_TENUM) { return fieldissignedc(c, t.lhs); }; + if (k == nkind.N_TNAME) { + let nm: str = t.str; + if (streq(nm, "bool")) { return false; }; + if (typenameisunsigned(nm)) { return false; }; + if (typenameissigned(nm)) { return true; }; + let al: *node = aliaslookup(c, nm); + if (al != nil) { return fieldissignedc(c, al); }; + let en: *enumtype = enumlookup(c, nm); + if (en != nil) { + if (en.storage != nil) { + return fieldissignedc(c, en.storage); + }; + return true; // default i32 storage is signed + }; + }; return false; }; // fieldloadop — pick the load instruction for a non-str struct -// field by its declared size + signedness. Mirrors the C cgen op -// dispatch (MOVZBQ for u8/bool/i8, MOVSXD for i32, MOVL for u32, MOVQ -// for 8-byte). f might be nil for fields outside our struct registry. -fn fieldloadop(f: *fieldinfo) str = { +// field by its declared size + signedness. Mirrors cstage's +// fldloadop: MOVZBQ/MOVSBQ for 1B, MOVZWQ/MOVSWQ for 2B, +// MOVL/MOVSXD for 4B, MOVQ for 8B. f might be nil for fields +// outside our struct registry. +fn fieldloadop(c: *cgen, f: *fieldinfo) str = { if (f == nil) { return "MOVQ"; }; let sz: i32 = f.fsz; - if (sz == 1) { return "MOVZBQ"; }; - if (sz == 4) { - let t: *node = f.tnode; - if (t != nil) { - if (t.kind == nkind.N_TNAME) { - if (typenameissigned(t.str)) { return "MOVSXD"; }; - }; - }; - return "MOVL"; - }; + let sigd: bool = fieldissignedc(c, f.tnode); + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; return "MOVQ"; }; // fieldstoreop — pick the store instruction for a non-str struct -// field by its declared size. MOVB for 1, MOVL for 4, MOVQ for 8. -fn fieldstoreop(f: *fieldinfo) str = { +// field by its declared size. MOVB for 1, MOVW for 2, MOVL for 4, +// MOVQ for 8. c kept in the signature for symmetry with fieldloadop. +fn fieldstoreop(c: *cgen, f: *fieldinfo) str = { if (f == nil) { return "MOVQ"; }; let sz: i32 = f.fsz; if (sz == 1) { return "MOVB"; }; + if (sz == 2) { return "MOVW"; }; if (sz == 4) { return "MOVL"; }; return "MOVQ"; }; +// tnodeloadop / tnodestoreop — same dispatch as fieldloadop / +// fieldstoreop but keyed on a raw type-AST node (tuple element type, +// pointer-target, slice-element, etc.) rather than a struct fieldinfo. +// Used at the index / tuple / pointer-deref sites where there's no +// fieldinfo entry but the type-node + size are both known. +fn tnodeloadop(c: *cgen, t: *node, sz: i32) str = { + let sigd: bool = fieldissignedc(c, t); + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; + return "MOVQ"; +}; + +fn tnodestoreop(c: *cgen, t: *node, sz: i32) str = { + if (sz == 1) { return "MOVB"; }; + if (sz == 2) { return "MOVW"; }; + if (sz == 4) { return "MOVL"; }; + return "MOVQ"; +}; + +// loadopsz — load op when the (size, signedness) pair has already +// been resolved upstream and the type-node isn't carried through. +// cgindex precomputes `signed_elem` via elemissignedc; cgforrange +// precomputes `bind_signed[b]` via paramissigned. Same dispatch as +// tnodeloadop's tail; only the keying differs. +fn loadopsz(sigd: bool, sz: i32) str = { + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; + return "MOVQ"; +}; + // indexbaseesz — element size for `arr[i]` where the base is a // chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice). // For str the element is one byte; for `[]T` / `*[]T` we drill into @@ -7667,7 +7767,7 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitoff((slot_off + 8 + fi.foff + 8): i64); emitline("(BP)\n"); } else { - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -8353,7 +8453,11 @@ fn cgcast(c: *cgen, n: *node) void = { let sz: i32 = primsize(nm); let is_unsigned: bool = typenameisunsigned(nm); let is_bool: bool = streq(nm, "bool"); - if (sz > 0) { if (sz < 8) { + // 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"); @@ -8364,16 +8468,17 @@ fn cgcast(c: *cgen, n: *node) void = { emitint(mask); emitline(", AX\n"); }; - } else { if (is_bool) { - emitline("\tANDQ\t$255, AX\n"); - } else { if (streq(nm, "i8")) { - emitline("\tMOVSBQ\tAX, AX\n"); - } else { if (streq(nm, "i16")) { - emitline("\tMOVSWQ\tAX, AX\n"); - } else { if (streq(nm, "i32")) { - emitline("\tMOVSXD\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"); + }; }; }; + }; + }; }; }; + if (is_bool) { emitline("\tANDQ\t$255, AX\n"); }; }; return; }; @@ -8550,7 +8655,7 @@ fn cgindex(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - signed_elem = elemissigned(baselocal.tnode); + signed_elem = elemissignedc(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); if (tn != nil) { @@ -8558,13 +8663,13 @@ fn cgindex(c: *cgen, n: *node) void = { isglobalarr = true; globalname = bn; esz = elemsizeofc(c, tn); - signed_elem = elemissigned(tn); + signed_elem = elemissignedc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; esz = elemsizeofc(c, tn); - signed_elem = elemissigned(tn); + signed_elem = elemissignedc(c, tn); }; }; }; @@ -8646,16 +8751,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(BX), AX\n"); } - else { emitline("\tMOVZWQ\t(BX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } - else { emitline("\tMOVL\t(BX), AX\n"); }; - } - else { emitline("\tMOVQ\t(BX), AX\n"); };};}; + let lop1: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop1); + emitline("\t(BX), AX\n"); return; }; if (baselocal != nil) { @@ -8693,16 +8792,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(BX), AX\n"); } - else { emitline("\tMOVZWQ\t(BX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } - else { emitline("\tMOVL\t(BX), AX\n"); }; - } - else { emitline("\tMOVQ\t(BX), AX\n"); };};}; + let lop2: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop2); + emitline("\t(BX), AX\n"); return; }; // Generic fallback when base isn't a plain ident. @@ -8728,16 +8821,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(AX), AX\n"); } - else { emitline("\tMOVZWQ\t(AX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } - else { emitline("\tMOVL\t(AX), AX\n"); }; - } - else { emitline("\tMOVQ\t(AX), AX\n"); };};}; + let lop3: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop3); + emitline("\t(AX), AX\n"); return; }; @@ -9157,7 +9244,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitdispreg(fi.foff: i64, "BX"); emitline(", X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -9199,7 +9286,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitoff((lc.off + fi.foff): i64); emitline("(BP), X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -9265,9 +9352,7 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; let sz: i32 = slotsize(c, tp); - let op: str = "MOVQ"; - if (sz == 1) { op = "MOVZBQ"; } - else { if (sz == 4) { op = "MOVL"; }; }; + let op: str = tnodeloadop(c, tp, sz); emitline("\t"); emitline(op); emitline("\t"); @@ -9402,7 +9487,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitdispreg(fi.foff: i64, "CX"); emitline(", X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -9464,7 +9549,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitline(", X0\n"); return; }; - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -9565,7 +9650,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; return; }; - let lop: str = fieldloadop(r.leaffi); + let lop: str = fieldloadop(c, r.leaffi); if (r.isglobal) { emitline("\tLEAQ\t"); emitsymname(c, r.rootname); @@ -9637,7 +9722,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitline(", X0\n"); return; }; - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -9735,7 +9820,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; return; }; - let lop: str = fieldloadop(ffi); + let lop: str = fieldloadop(c, ffi); if (isptr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); @@ -10147,7 +10232,7 @@ fn cgalloc(c: *cgen, n: *node) void = { fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -10201,8 +10286,14 @@ fn cgappend(c: *cgen, n: *node) void = { if (snlocal == nil) { return; }; let sn_off: i32 = snlocal.off; let esz: i32 = elemsizeof(snlocal.tnode); - let store_op: str = "MOVQ"; - if (esz == 1) { store_op = "MOVB"; }; + let etnode: *node = nil; + if (snlocal.tnode != nil) { + let stk: nkind = snlocal.tnode.kind; + if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; }; + if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; }; + if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; }; + }; + let store_op: str = tnodestoreop(c, etnode, esz); let vn: *node = sn.next; for (vn != nil) { @@ -10213,8 +10304,7 @@ fn cgappend(c: *cgen, n: *node) void = { let itlocal: *local = localfindnode(c, it.str); if (itlocal == nil) { vn = vn.next; continue; }; let it_off: i32 = itlocal.off; - let load_op: str = "MOVQ"; - if (esz == 1) { load_op = "MOVZBQ"; }; + let load_op: str = tnodeloadop(c, etnode, esz); emitline("\tSUBQ\t$8, SP\n"); emitline("\tMOVQ\t$0, (SP)\n"); let ll: str = mklabel(c, "spr_l"); @@ -10444,9 +10534,7 @@ fn cgcall(c: *cgen, n: *node) void = { emitline("(BP)\n"); } else { cgexpr(c, aa2); - let op: str = "MOVQ"; - if (esz == 1) { op = "MOVB"; } - else { if (esz == 4) { op = "MOVL"; }; }; + let op: str = tnodestoreop(c, varp.lhs, esz); emitline("\t"); emitline(op); emitline("\tAX, "); @@ -10795,19 +10883,10 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TPTR) { let pe: *node = tn.lhs; if (pe != nil) { - if (pe.kind == nkind.N_TNAME) { - let ps: i32 = primsize(pe.str); - if (ps == 1) { - loadop = "MOVZBQ"; - storeop = "MOVB"; - } else { if (ps == 4) { - if (typenameissigned(pe.str)) { - loadop = "MOVSXD"; - } else { - loadop = "MOVL"; - }; - storeop = "MOVL"; - }; }; + let ps: i32 = fieldsize(c, pe); + if (ps == 1 || ps == 2 || ps == 4) { + loadop = tnodeloadop(c, pe, ps); + storeop = tnodestoreop(c, pe, ps); }; }; }; @@ -11011,10 +11090,10 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { if (esz == 2) { emitline("\tMOVW\tAX, (BX)\n"); } - else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); };};}; + let isop: str = tnodestoreop(c, elemtn, esz); + emitline("\t"); + emitline(isop); + emitline("\tAX, (BX)\n"); return; }; }; @@ -11054,7 +11133,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -11110,7 +11189,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -11158,7 +11237,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); return; }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -11283,7 +11362,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\n"); return; }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -11298,7 +11377,7 @@ fn cgassign(c: *cgen, n: *node) void = { // → push → eval rhs → combine → // store. cgexpr clobbers BX, so // re-LEAQ for the store. - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -11315,7 +11394,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -11406,7 +11485,7 @@ fn cgassign(c: *cgen, n: *node) void = { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -11496,7 +11575,7 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - let sop: str = fieldstoreop(r.leaffi); + let sop: str = fieldstoreop(c, r.leaffi); cgexpr(c, n.rhs); if (r.isglobal) { emitline("\tLEAQ\t"); @@ -11609,7 +11688,7 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - let sop: str = fieldstoreop(ffi); + let sop: str = fieldstoreop(c, ffi); if (isptr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); @@ -12288,9 +12367,7 @@ fn cglet(c: *cgen, n: *node) void = { if (ps > 0) { esz = ps; }; }; }; - let mop: str = "MOVQ"; - if (esz == 1) { mop = "MOVB"; } - else { if (esz == 4) { mop = "MOVL"; }; }; + let mop: str = tnodestoreop(c, elemn, esz); let idx: i32 = 0; let repeat: bool = false; let e: *node = rhs.list; @@ -12406,7 +12483,7 @@ fn cglet(c: *cgen, n: *node) void = { emitline("(BP)\n"); fi = nil; } else { - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -12721,16 +12798,11 @@ fn paramfieldsize(t: *node) i32 = { return 8; }; -// paramissigned — does this primitive type need sign-extending on a -// narrow (4B) load? Mirrors C cgen's `binds[b].signed_field` flag. -fn paramissigned(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - let nm: str = t.str; - if (streq(nm, "i8")) { return true; }; - if (streq(nm, "i16")) { return true; }; - if (streq(nm, "i32")) { return true; }; - return false; +// paramissigned — does this type need sign-extending on a sub-word +// (1/2/4B) load? Mirrors cstage's signed_field check via +// fieldissignedc (resolves TBANG / TENUM / alias chains). +fn paramissigned(c: *cgen, t: *node) bool = { + return fieldissignedc(c, t); }; // cgforrange — lower `for (let x .. slice) body` (and the tuple- @@ -12804,7 +12876,7 @@ fn cgforrange(c: *cgen, n: *node) void = { let signf: bool = false; if (tp != nil) { fsz = paramfieldsize(tp); - signf = paramissigned(tp); + signf = paramissigned(c, tp); }; let slot_sz: i32 = fsz; if (slot_sz < 8) { slot_sz = 8; }; @@ -12832,7 +12904,7 @@ fn cgforrange(c: *cgen, n: *node) void = { // reads `u->sub->kind` for the elem type. bind_signed[0] = false; if (elemt != nil) { - bind_signed[0] = paramissigned(elemt); + bind_signed[0] = paramissigned(c, elemt); }; if (n.str.len > 0) { // Register with elem tnode so x.field on a loop @@ -12930,15 +13002,12 @@ fn cgforrange(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); - // Per-binding load from BX+foff. + // Per-binding load from BX+foff. Signedness comes from bind_signed + // (set via paramissigned → fieldissignedc), so enum-aliased narrows + // pick the right MOVS*Q without a literal-name gate. let b: i32 = 0; for (b < nbinds) { - let op: str = "MOVQ"; - if (bind_sz[b] == 1) { op = "MOVZBQ"; } - else { if (bind_sz[b] == 4) { - if (bind_signed[b]) { op = "MOVSXD"; } - else { op = "MOVL"; }; - };}; + let op: str = loadopsz(bind_signed[b], bind_sz[b]); emitline("\t"); emitline(op); emitline("\t"); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 72678036..aeef97c1 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -403,7 +403,11 @@ fn cgcast(c: *cgen, n: *node) void = { let sz: i32 = primsize(nm); let is_unsigned: bool = typenameisunsigned(nm); let is_bool: bool = streq(nm, "bool"); - if (sz > 0) { if (sz < 8) { + // 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"); @@ -414,16 +418,17 @@ fn cgcast(c: *cgen, n: *node) void = { emitint(mask); emitline(", AX\n"); }; - } else { if (is_bool) { - emitline("\tANDQ\t$255, AX\n"); - } else { if (streq(nm, "i8")) { - emitline("\tMOVSBQ\tAX, AX\n"); - } else { if (streq(nm, "i16")) { - emitline("\tMOVSWQ\tAX, AX\n"); - } else { if (streq(nm, "i32")) { - emitline("\tMOVSXD\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"); + }; }; }; + }; + }; }; }; + if (is_bool) { emitline("\tANDQ\t$255, AX\n"); }; }; return; }; @@ -600,7 +605,7 @@ fn cgindex(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - signed_elem = elemissigned(baselocal.tnode); + signed_elem = elemissignedc(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); if (tn != nil) { @@ -608,13 +613,13 @@ fn cgindex(c: *cgen, n: *node) void = { isglobalarr = true; globalname = bn; esz = elemsizeofc(c, tn); - signed_elem = elemissigned(tn); + signed_elem = elemissignedc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; esz = elemsizeofc(c, tn); - signed_elem = elemissigned(tn); + signed_elem = elemissignedc(c, tn); }; }; }; @@ -696,16 +701,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(BX), AX\n"); } - else { emitline("\tMOVZWQ\t(BX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } - else { emitline("\tMOVL\t(BX), AX\n"); }; - } - else { emitline("\tMOVQ\t(BX), AX\n"); };};}; + let lop1: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop1); + emitline("\t(BX), AX\n"); return; }; if (baselocal != nil) { @@ -743,16 +742,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(BX), AX\n"); } - else { emitline("\tMOVZWQ\t(BX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } - else { emitline("\tMOVL\t(BX), AX\n"); }; - } - else { emitline("\tMOVQ\t(BX), AX\n"); };};}; + let lop2: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop2); + emitline("\t(BX), AX\n"); return; }; // Generic fallback when base isn't a plain ident. @@ -778,16 +771,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(AX), AX\n"); } - else { emitline("\tMOVZWQ\t(AX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } - else { emitline("\tMOVL\t(AX), AX\n"); }; - } - else { emitline("\tMOVQ\t(AX), AX\n"); };};}; + let lop3: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop3); + emitline("\t(AX), AX\n"); return; }; @@ -1207,7 +1194,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitdispreg(fi.foff: i64, "BX"); emitline(", X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -1249,7 +1236,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitoff((lc.off + fi.foff): i64); emitline("(BP), X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -1315,9 +1302,7 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; let sz: i32 = slotsize(c, tp); - let op: str = "MOVQ"; - if (sz == 1) { op = "MOVZBQ"; } - else { if (sz == 4) { op = "MOVL"; }; }; + let op: str = tnodeloadop(c, tp, sz); emitline("\t"); emitline(op); emitline("\t"); @@ -1452,7 +1437,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitdispreg(fi.foff: i64, "CX"); emitline(", X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -1514,7 +1499,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitline(", X0\n"); return; }; - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -1615,7 +1600,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; return; }; - let lop: str = fieldloadop(r.leaffi); + let lop: str = fieldloadop(c, r.leaffi); if (r.isglobal) { emitline("\tLEAQ\t"); emitsymname(c, r.rootname); @@ -1687,7 +1672,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitline(", X0\n"); return; }; - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -1785,7 +1770,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; return; }; - let lop: str = fieldloadop(ffi); + let lop: str = fieldloadop(c, ffi); if (isptr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); @@ -2197,7 +2182,7 @@ fn cgalloc(c: *cgen, n: *node) void = { fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -2251,8 +2236,14 @@ fn cgappend(c: *cgen, n: *node) void = { if (snlocal == nil) { return; }; let sn_off: i32 = snlocal.off; let esz: i32 = elemsizeof(snlocal.tnode); - let store_op: str = "MOVQ"; - if (esz == 1) { store_op = "MOVB"; }; + let etnode: *node = nil; + if (snlocal.tnode != nil) { + let stk: nkind = snlocal.tnode.kind; + if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; }; + if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; }; + if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; }; + }; + let store_op: str = tnodestoreop(c, etnode, esz); let vn: *node = sn.next; for (vn != nil) { @@ -2263,8 +2254,7 @@ fn cgappend(c: *cgen, n: *node) void = { let itlocal: *local = localfindnode(c, it.str); if (itlocal == nil) { vn = vn.next; continue; }; let it_off: i32 = itlocal.off; - let load_op: str = "MOVQ"; - if (esz == 1) { load_op = "MOVZBQ"; }; + let load_op: str = tnodeloadop(c, etnode, esz); emitline("\tSUBQ\t$8, SP\n"); emitline("\tMOVQ\t$0, (SP)\n"); let ll: str = mklabel(c, "spr_l"); @@ -2494,9 +2484,7 @@ fn cgcall(c: *cgen, n: *node) void = { emitline("(BP)\n"); } else { cgexpr(c, aa2); - let op: str = "MOVQ"; - if (esz == 1) { op = "MOVB"; } - else { if (esz == 4) { op = "MOVL"; }; }; + let op: str = tnodestoreop(c, varp.lhs, esz); emitline("\t"); emitline(op); emitline("\tAX, "); @@ -2845,19 +2833,10 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TPTR) { let pe: *node = tn.lhs; if (pe != nil) { - if (pe.kind == nkind.N_TNAME) { - let ps: i32 = primsize(pe.str); - if (ps == 1) { - loadop = "MOVZBQ"; - storeop = "MOVB"; - } else { if (ps == 4) { - if (typenameissigned(pe.str)) { - loadop = "MOVSXD"; - } else { - loadop = "MOVL"; - }; - storeop = "MOVL"; - }; }; + let ps: i32 = fieldsize(c, pe); + if (ps == 1 || ps == 2 || ps == 4) { + loadop = tnodeloadop(c, pe, ps); + storeop = tnodestoreop(c, pe, ps); }; }; }; @@ -3061,10 +3040,10 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { if (esz == 2) { emitline("\tMOVW\tAX, (BX)\n"); } - else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); };};}; + let isop: str = tnodestoreop(c, elemtn, esz); + emitline("\t"); + emitline(isop); + emitline("\tAX, (BX)\n"); return; }; }; @@ -3104,7 +3083,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -3160,7 +3139,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -3208,7 +3187,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); return; }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -3333,7 +3312,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\n"); return; }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -3348,7 +3327,7 @@ fn cgassign(c: *cgen, n: *node) void = { // → push → eval rhs → combine → // store. cgexpr clobbers BX, so // re-LEAQ for the store. - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -3365,7 +3344,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -3456,7 +3435,7 @@ fn cgassign(c: *cgen, n: *node) void = { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -3546,7 +3525,7 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - let sop: str = fieldstoreop(r.leaffi); + let sop: str = fieldstoreop(c, r.leaffi); cgexpr(c, n.rhs); if (r.isglobal) { emitline("\tLEAQ\t"); @@ -3659,7 +3638,7 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - let sop: str = fieldstoreop(ffi); + let sop: str = fieldstoreop(c, ffi); if (isptr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index f7e4fb24..3f14e232 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -373,9 +373,7 @@ fn cglet(c: *cgen, n: *node) void = { if (ps > 0) { esz = ps; }; }; }; - let mop: str = "MOVQ"; - if (esz == 1) { mop = "MOVB"; } - else { if (esz == 4) { mop = "MOVL"; }; }; + let mop: str = tnodestoreop(c, elemn, esz); let idx: i32 = 0; let repeat: bool = false; let e: *node = rhs.list; @@ -491,7 +489,7 @@ fn cglet(c: *cgen, n: *node) void = { emitline("(BP)\n"); fi = nil; } else { - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -806,16 +804,11 @@ fn paramfieldsize(t: *node) i32 = { return 8; }; -// paramissigned — does this primitive type need sign-extending on a -// narrow (4B) load? Mirrors C cgen's `binds[b].signed_field` flag. -fn paramissigned(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - let nm: str = t.str; - if (streq(nm, "i8")) { return true; }; - if (streq(nm, "i16")) { return true; }; - if (streq(nm, "i32")) { return true; }; - return false; +// paramissigned — does this type need sign-extending on a sub-word +// (1/2/4B) load? Mirrors cstage's signed_field check via +// fieldissignedc (resolves TBANG / TENUM / alias chains). +fn paramissigned(c: *cgen, t: *node) bool = { + return fieldissignedc(c, t); }; // cgforrange — lower `for (let x .. slice) body` (and the tuple- @@ -889,7 +882,7 @@ fn cgforrange(c: *cgen, n: *node) void = { let signf: bool = false; if (tp != nil) { fsz = paramfieldsize(tp); - signf = paramissigned(tp); + signf = paramissigned(c, tp); }; let slot_sz: i32 = fsz; if (slot_sz < 8) { slot_sz = 8; }; @@ -917,7 +910,7 @@ fn cgforrange(c: *cgen, n: *node) void = { // reads `u->sub->kind` for the elem type. bind_signed[0] = false; if (elemt != nil) { - bind_signed[0] = paramissigned(elemt); + bind_signed[0] = paramissigned(c, elemt); }; if (n.str.len > 0) { // Register with elem tnode so x.field on a loop @@ -1015,15 +1008,12 @@ fn cgforrange(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); - // Per-binding load from BX+foff. + // Per-binding load from BX+foff. Signedness comes from bind_signed + // (set via paramissigned → fieldissignedc), so enum-aliased narrows + // pick the right MOVS*Q without a literal-name gate. let b: i32 = 0; for (b < nbinds) { - let op: str = "MOVQ"; - if (bind_sz[b] == 1) { op = "MOVZBQ"; } - else { if (bind_sz[b] == 4) { - if (bind_signed[b]) { op = "MOVSXD"; } - else { op = "MOVL"; }; - };}; + let op: str = loadopsz(bind_signed[b], bind_sz[b]); emitline("\t"); emitline(op); emitline("\t"); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 9468d16a..06deb6a0 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -502,7 +502,10 @@ fn nodeisstr(c: *cgen, n: *node) bool = { return false; }; -// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr. +// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr/rune. +// rune is a Unicode codepoint (0..0x10FFFF); cgen treats it as +// unsigned so narrow-cast / sub-word load paths zero-extend (MOVL, +// not MOVSXD). Mirrors cstage's type_isunsigned post task #5. fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; @@ -510,10 +513,39 @@ fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u64")) { return true; }; if (streq(nm, "uint")) { return true; }; if (streq(nm, "uintptr")) { return true; }; + if (streq(nm, "rune")) { return true; }; return false; }; -// typenodeisunsigned — recurse through TNAME / TPTR / TSLICE etc. +// typenodeisunsigned — recurse through TNAME aliases / TBANG / TENUM +// to the resolved primitive. Mirrors cstage's type_isunsigned which +// recurses into TY_NAMED.under and TY_ENUM.sub. +fn typenodeisunsignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + if (k == nkind.N_TBANG) { return typenodeisunsignedc(c, t.lhs); }; + if (k == nkind.N_TENUM) { return typenodeisunsignedc(c, t.lhs); }; + if (k == nkind.N_TNAME) { + let nm: str = t.str; + if (typenameisunsigned(nm)) { return true; }; + if (typenameissigned(nm)) { return false; }; + // Follow aliases / enum storage. + let al: *node = aliaslookup(c, nm); + if (al != nil) { return typenodeisunsignedc(c, al); }; + let en: *enumtype = enumlookup(c, nm); + if (en != nil) { + if (en.storage != nil) { + return typenodeisunsignedc(c, en.storage); + }; + return false; // default storage i32 is signed + }; + }; + return false; +}; + +// typenodeisunsigned — legacy callers without *cgen context. Only +// resolves primitive TNAMEs (no alias/enum recursion); use the +// _c variant where the cgen registry is in scope. fn typenodeisunsigned(t: *node) bool = { if (t == nil) { return false; }; if (t.kind == nkind.N_TNAME) { return typenameisunsigned(t.str); }; @@ -568,9 +600,21 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { }; // elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is -// its element a signed narrow primitive (i8/i16/i32/rune)? Used by -// cgindex to pick MOVSXD vs MOVL at esz=4. Mirrors C cgen's -// `signed_elem` check. +// its element a signed narrow primitive (i8/i16/i32)? Used by +// cgindex to pick MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at +// esz=1/2). Mirrors cstage's `signed_elem`. Follows alias/enum +// chains so `[]Alias` arrays resolve to the underlying signedness. +fn elemissignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let elem: *node = nil; + let k: nkind = t.kind; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + return fieldissignedc(c, elem); +}; + fn elemissigned(t: *node) bool = { if (t == nil) { return false; }; let elem: *node = nil; @@ -583,47 +627,103 @@ fn elemissigned(t: *node) bool = { return typenameissigned(elem.str); }; -// typenameissigned — true for i8/i16/i32/i64/int/rune. +// typenameissigned — true for i8/i16/i32/i64/int. rune is excluded +// (it's a non-negative Unicode codepoint, treated as unsigned). fn typenameissigned(nm: str) bool = { if (streq(nm, "i8")) { return true; }; if (streq(nm, "i16")) { return true; }; if (streq(nm, "i32")) { return true; }; if (streq(nm, "i64")) { return true; }; if (streq(nm, "int")) { return true; }; - if (streq(nm, "rune")) { return true; }; + return false; +}; + +// fieldissignedc — does this field/element type need sign-extension +// on a sub-word load? Walks TBANG / TENUM / TNAME-aliases to the +// resolved primitive. Mirrors cstage's fld_issigned: bool is treated +// as unsigned (0/1 ⇒ MOVZBQ); rune is unsigned (codepoint ⇒ MOVL). +fn fieldissignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + if (k == nkind.N_TBANG) { return fieldissignedc(c, t.lhs); }; + if (k == nkind.N_TENUM) { return fieldissignedc(c, t.lhs); }; + if (k == nkind.N_TNAME) { + let nm: str = t.str; + if (streq(nm, "bool")) { return false; }; + if (typenameisunsigned(nm)) { return false; }; + if (typenameissigned(nm)) { return true; }; + let al: *node = aliaslookup(c, nm); + if (al != nil) { return fieldissignedc(c, al); }; + let en: *enumtype = enumlookup(c, nm); + if (en != nil) { + if (en.storage != nil) { + return fieldissignedc(c, en.storage); + }; + return true; // default i32 storage is signed + }; + }; return false; }; // fieldloadop — pick the load instruction for a non-str struct -// field by its declared size + signedness. Mirrors the C cgen op -// dispatch (MOVZBQ for u8/bool/i8, MOVSXD for i32, MOVL for u32, MOVQ -// for 8-byte). f might be nil for fields outside our struct registry. -fn fieldloadop(f: *fieldinfo) str = { +// field by its declared size + signedness. Mirrors cstage's +// fldloadop: MOVZBQ/MOVSBQ for 1B, MOVZWQ/MOVSWQ for 2B, +// MOVL/MOVSXD for 4B, MOVQ for 8B. f might be nil for fields +// outside our struct registry. +fn fieldloadop(c: *cgen, f: *fieldinfo) str = { if (f == nil) { return "MOVQ"; }; let sz: i32 = f.fsz; - if (sz == 1) { return "MOVZBQ"; }; - if (sz == 4) { - let t: *node = f.tnode; - if (t != nil) { - if (t.kind == nkind.N_TNAME) { - if (typenameissigned(t.str)) { return "MOVSXD"; }; - }; - }; - return "MOVL"; - }; + let sigd: bool = fieldissignedc(c, f.tnode); + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; return "MOVQ"; }; // fieldstoreop — pick the store instruction for a non-str struct -// field by its declared size. MOVB for 1, MOVL for 4, MOVQ for 8. -fn fieldstoreop(f: *fieldinfo) str = { +// field by its declared size. MOVB for 1, MOVW for 2, MOVL for 4, +// MOVQ for 8. c kept in the signature for symmetry with fieldloadop. +fn fieldstoreop(c: *cgen, f: *fieldinfo) str = { if (f == nil) { return "MOVQ"; }; let sz: i32 = f.fsz; if (sz == 1) { return "MOVB"; }; + if (sz == 2) { return "MOVW"; }; if (sz == 4) { return "MOVL"; }; return "MOVQ"; }; +// tnodeloadop / tnodestoreop — same dispatch as fieldloadop / +// fieldstoreop but keyed on a raw type-AST node (tuple element type, +// pointer-target, slice-element, etc.) rather than a struct fieldinfo. +// Used at the index / tuple / pointer-deref sites where there's no +// fieldinfo entry but the type-node + size are both known. +fn tnodeloadop(c: *cgen, t: *node, sz: i32) str = { + let sigd: bool = fieldissignedc(c, t); + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; + return "MOVQ"; +}; + +fn tnodestoreop(c: *cgen, t: *node, sz: i32) str = { + if (sz == 1) { return "MOVB"; }; + if (sz == 2) { return "MOVW"; }; + if (sz == 4) { return "MOVL"; }; + return "MOVQ"; +}; + +// loadopsz — load op when the (size, signedness) pair has already +// been resolved upstream and the type-node isn't carried through. +// cgindex precomputes `signed_elem` via elemissignedc; cgforrange +// precomputes `bind_signed[b]` via paramissigned. Same dispatch as +// tnodeloadop's tail; only the keying differs. +fn loadopsz(sigd: bool, sz: i32) str = { + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; + return "MOVQ"; +}; + // indexbaseesz — element size for `arr[i]` where the base is a // chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice). // For str the element is one byte; for `[]T` / `*[]T` we drill into @@ -2097,7 +2197,7 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitoff((slot_off + 8 + fi.foff + 8): i64); emitline("(BP)\n"); } else { - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index f5b90ede..41fe98cd 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6072,7 +6072,10 @@ fn nodeisstr(c: *cgen, n: *node) bool = { return false; }; -// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr. +// typenameisunsigned — true for u8/u16/u32/u64/uint/uintptr/rune. +// rune is a Unicode codepoint (0..0x10FFFF); cgen treats it as +// unsigned so narrow-cast / sub-word load paths zero-extend (MOVL, +// not MOVSXD). Mirrors cstage's type_isunsigned post task #5. fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; @@ -6080,10 +6083,39 @@ fn typenameisunsigned(nm: str) bool = { if (streq(nm, "u64")) { return true; }; if (streq(nm, "uint")) { return true; }; if (streq(nm, "uintptr")) { return true; }; + if (streq(nm, "rune")) { return true; }; return false; }; -// typenodeisunsigned — recurse through TNAME / TPTR / TSLICE etc. +// typenodeisunsigned — recurse through TNAME aliases / TBANG / TENUM +// to the resolved primitive. Mirrors cstage's type_isunsigned which +// recurses into TY_NAMED.under and TY_ENUM.sub. +fn typenodeisunsignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + if (k == nkind.N_TBANG) { return typenodeisunsignedc(c, t.lhs); }; + if (k == nkind.N_TENUM) { return typenodeisunsignedc(c, t.lhs); }; + if (k == nkind.N_TNAME) { + let nm: str = t.str; + if (typenameisunsigned(nm)) { return true; }; + if (typenameissigned(nm)) { return false; }; + // Follow aliases / enum storage. + let al: *node = aliaslookup(c, nm); + if (al != nil) { return typenodeisunsignedc(c, al); }; + let en: *enumtype = enumlookup(c, nm); + if (en != nil) { + if (en.storage != nil) { + return typenodeisunsignedc(c, en.storage); + }; + return false; // default storage i32 is signed + }; + }; + return false; +}; + +// typenodeisunsigned — legacy callers without *cgen context. Only +// resolves primitive TNAMEs (no alias/enum recursion); use the +// _c variant where the cgen registry is in scope. fn typenodeisunsigned(t: *node) bool = { if (t == nil) { return false; }; if (t.kind == nkind.N_TNAME) { return typenameisunsigned(t.str); }; @@ -6138,9 +6170,21 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { }; // elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is -// its element a signed narrow primitive (i8/i16/i32/rune)? Used by -// cgindex to pick MOVSXD vs MOVL at esz=4. Mirrors C cgen's -// `signed_elem` check. +// its element a signed narrow primitive (i8/i16/i32)? Used by +// cgindex to pick MOVSXD vs MOVL at esz=4 (and MOVSBQ/MOVSWQ at +// esz=1/2). Mirrors cstage's `signed_elem`. Follows alias/enum +// chains so `[]Alias` arrays resolve to the underlying signedness. +fn elemissignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let elem: *node = nil; + let k: nkind = t.kind; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + return fieldissignedc(c, elem); +}; + fn elemissigned(t: *node) bool = { if (t == nil) { return false; }; let elem: *node = nil; @@ -6153,47 +6197,103 @@ fn elemissigned(t: *node) bool = { return typenameissigned(elem.str); }; -// typenameissigned — true for i8/i16/i32/i64/int/rune. +// typenameissigned — true for i8/i16/i32/i64/int. rune is excluded +// (it's a non-negative Unicode codepoint, treated as unsigned). fn typenameissigned(nm: str) bool = { if (streq(nm, "i8")) { return true; }; if (streq(nm, "i16")) { return true; }; if (streq(nm, "i32")) { return true; }; if (streq(nm, "i64")) { return true; }; if (streq(nm, "int")) { return true; }; - if (streq(nm, "rune")) { return true; }; + return false; +}; + +// fieldissignedc — does this field/element type need sign-extension +// on a sub-word load? Walks TBANG / TENUM / TNAME-aliases to the +// resolved primitive. Mirrors cstage's fld_issigned: bool is treated +// as unsigned (0/1 ⇒ MOVZBQ); rune is unsigned (codepoint ⇒ MOVL). +fn fieldissignedc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + if (k == nkind.N_TBANG) { return fieldissignedc(c, t.lhs); }; + if (k == nkind.N_TENUM) { return fieldissignedc(c, t.lhs); }; + if (k == nkind.N_TNAME) { + let nm: str = t.str; + if (streq(nm, "bool")) { return false; }; + if (typenameisunsigned(nm)) { return false; }; + if (typenameissigned(nm)) { return true; }; + let al: *node = aliaslookup(c, nm); + if (al != nil) { return fieldissignedc(c, al); }; + let en: *enumtype = enumlookup(c, nm); + if (en != nil) { + if (en.storage != nil) { + return fieldissignedc(c, en.storage); + }; + return true; // default i32 storage is signed + }; + }; return false; }; // fieldloadop — pick the load instruction for a non-str struct -// field by its declared size + signedness. Mirrors the C cgen op -// dispatch (MOVZBQ for u8/bool/i8, MOVSXD for i32, MOVL for u32, MOVQ -// for 8-byte). f might be nil for fields outside our struct registry. -fn fieldloadop(f: *fieldinfo) str = { +// field by its declared size + signedness. Mirrors cstage's +// fldloadop: MOVZBQ/MOVSBQ for 1B, MOVZWQ/MOVSWQ for 2B, +// MOVL/MOVSXD for 4B, MOVQ for 8B. f might be nil for fields +// outside our struct registry. +fn fieldloadop(c: *cgen, f: *fieldinfo) str = { if (f == nil) { return "MOVQ"; }; let sz: i32 = f.fsz; - if (sz == 1) { return "MOVZBQ"; }; - if (sz == 4) { - let t: *node = f.tnode; - if (t != nil) { - if (t.kind == nkind.N_TNAME) { - if (typenameissigned(t.str)) { return "MOVSXD"; }; - }; - }; - return "MOVL"; - }; + let sigd: bool = fieldissignedc(c, f.tnode); + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; return "MOVQ"; }; // fieldstoreop — pick the store instruction for a non-str struct -// field by its declared size. MOVB for 1, MOVL for 4, MOVQ for 8. -fn fieldstoreop(f: *fieldinfo) str = { +// field by its declared size. MOVB for 1, MOVW for 2, MOVL for 4, +// MOVQ for 8. c kept in the signature for symmetry with fieldloadop. +fn fieldstoreop(c: *cgen, f: *fieldinfo) str = { if (f == nil) { return "MOVQ"; }; let sz: i32 = f.fsz; if (sz == 1) { return "MOVB"; }; + if (sz == 2) { return "MOVW"; }; if (sz == 4) { return "MOVL"; }; return "MOVQ"; }; +// tnodeloadop / tnodestoreop — same dispatch as fieldloadop / +// fieldstoreop but keyed on a raw type-AST node (tuple element type, +// pointer-target, slice-element, etc.) rather than a struct fieldinfo. +// Used at the index / tuple / pointer-deref sites where there's no +// fieldinfo entry but the type-node + size are both known. +fn tnodeloadop(c: *cgen, t: *node, sz: i32) str = { + let sigd: bool = fieldissignedc(c, t); + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; + return "MOVQ"; +}; + +fn tnodestoreop(c: *cgen, t: *node, sz: i32) str = { + if (sz == 1) { return "MOVB"; }; + if (sz == 2) { return "MOVW"; }; + if (sz == 4) { return "MOVL"; }; + return "MOVQ"; +}; + +// loadopsz — load op when the (size, signedness) pair has already +// been resolved upstream and the type-node isn't carried through. +// cgindex precomputes `signed_elem` via elemissignedc; cgforrange +// precomputes `bind_signed[b]` via paramissigned. Same dispatch as +// tnodeloadop's tail; only the keying differs. +fn loadopsz(sigd: bool, sz: i32) str = { + if (sz == 1) { if (sigd) { return "MOVSBQ"; }; return "MOVZBQ"; }; + if (sz == 2) { if (sigd) { return "MOVSWQ"; }; return "MOVZWQ"; }; + if (sz == 4) { if (sigd) { return "MOVSXD"; }; return "MOVL"; }; + return "MOVQ"; +}; + // indexbaseesz — element size for `arr[i]` where the base is a // chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice). // For str the element is one byte; for `[]T` / `*[]T` we drill into @@ -7667,7 +7767,7 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: emitoff((slot_off + 8 + fi.foff + 8): i64); emitline("(BP)\n"); } else { - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -8353,7 +8453,11 @@ fn cgcast(c: *cgen, n: *node) void = { let sz: i32 = primsize(nm); let is_unsigned: bool = typenameisunsigned(nm); let is_bool: bool = streq(nm, "bool"); - if (sz > 0) { if (sz < 8) { + // 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"); @@ -8364,16 +8468,17 @@ fn cgcast(c: *cgen, n: *node) void = { emitint(mask); emitline(", AX\n"); }; - } else { if (is_bool) { - emitline("\tANDQ\t$255, AX\n"); - } else { if (streq(nm, "i8")) { - emitline("\tMOVSBQ\tAX, AX\n"); - } else { if (streq(nm, "i16")) { - emitline("\tMOVSWQ\tAX, AX\n"); - } else { if (streq(nm, "i32")) { - emitline("\tMOVSXD\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"); + }; }; }; + }; + }; }; }; + if (is_bool) { emitline("\tANDQ\t$255, AX\n"); }; }; return; }; @@ -8550,7 +8655,7 @@ fn cgindex(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - signed_elem = elemissigned(baselocal.tnode); + signed_elem = elemissignedc(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); if (tn != nil) { @@ -8558,13 +8663,13 @@ fn cgindex(c: *cgen, n: *node) void = { isglobalarr = true; globalname = bn; esz = elemsizeofc(c, tn); - signed_elem = elemissigned(tn); + signed_elem = elemissignedc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; esz = elemsizeofc(c, tn); - signed_elem = elemissigned(tn); + signed_elem = elemissignedc(c, tn); }; }; }; @@ -8646,16 +8751,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(BX), AX\n"); } - else { emitline("\tMOVZWQ\t(BX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } - else { emitline("\tMOVL\t(BX), AX\n"); }; - } - else { emitline("\tMOVQ\t(BX), AX\n"); };};}; + let lop1: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop1); + emitline("\t(BX), AX\n"); return; }; if (baselocal != nil) { @@ -8693,16 +8792,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(BX), AX\n"); } - else { emitline("\tMOVZWQ\t(BX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } - else { emitline("\tMOVL\t(BX), AX\n"); }; - } - else { emitline("\tMOVQ\t(BX), AX\n"); };};}; + let lop2: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop2); + emitline("\t(BX), AX\n"); return; }; // Generic fallback when base isn't a plain ident. @@ -8728,16 +8821,10 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { if (esz == 2) { - if (signed_elem) { emitline("\tMOVSWQ\t(AX), AX\n"); } - else { emitline("\tMOVZWQ\t(AX), AX\n"); }; - } - else { if (esz == 4) { - if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } - else { emitline("\tMOVL\t(AX), AX\n"); }; - } - else { emitline("\tMOVQ\t(AX), AX\n"); };};}; + let lop3: str = loadopsz(signed_elem, esz); + emitline("\t"); + emitline(lop3); + emitline("\t(AX), AX\n"); return; }; @@ -9157,7 +9244,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitdispreg(fi.foff: i64, "BX"); emitline(", X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -9199,7 +9286,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitoff((lc.off + fi.foff): i64); emitline("(BP), X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -9265,9 +9352,7 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; let sz: i32 = slotsize(c, tp); - let op: str = "MOVQ"; - if (sz == 1) { op = "MOVZBQ"; } - else { if (sz == 4) { op = "MOVL"; }; }; + let op: str = tnodeloadop(c, tp, sz); emitline("\t"); emitline(op); emitline("\t"); @@ -9402,7 +9487,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitdispreg(fi.foff: i64, "CX"); emitline(", X0\n"); } else { - let op: str = fieldloadop(fi); + let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); @@ -9464,7 +9549,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitline(", X0\n"); return; }; - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -9565,7 +9650,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; return; }; - let lop: str = fieldloadop(r.leaffi); + let lop: str = fieldloadop(c, r.leaffi); if (r.isglobal) { emitline("\tLEAQ\t"); emitsymname(c, r.rootname); @@ -9637,7 +9722,7 @@ fn cgdot(c: *cgen, n: *node) void = { emitline(", X0\n"); return; }; - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -9735,7 +9820,7 @@ fn cgdot(c: *cgen, n: *node) void = { }; return; }; - let lop: str = fieldloadop(ffi); + let lop: str = fieldloadop(c, ffi); if (isptr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); @@ -10147,7 +10232,7 @@ fn cgalloc(c: *cgen, n: *node) void = { fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -10201,8 +10286,14 @@ fn cgappend(c: *cgen, n: *node) void = { if (snlocal == nil) { return; }; let sn_off: i32 = snlocal.off; let esz: i32 = elemsizeof(snlocal.tnode); - let store_op: str = "MOVQ"; - if (esz == 1) { store_op = "MOVB"; }; + let etnode: *node = nil; + if (snlocal.tnode != nil) { + let stk: nkind = snlocal.tnode.kind; + if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; }; + if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; }; + if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; }; + }; + let store_op: str = tnodestoreop(c, etnode, esz); let vn: *node = sn.next; for (vn != nil) { @@ -10213,8 +10304,7 @@ fn cgappend(c: *cgen, n: *node) void = { let itlocal: *local = localfindnode(c, it.str); if (itlocal == nil) { vn = vn.next; continue; }; let it_off: i32 = itlocal.off; - let load_op: str = "MOVQ"; - if (esz == 1) { load_op = "MOVZBQ"; }; + let load_op: str = tnodeloadop(c, etnode, esz); emitline("\tSUBQ\t$8, SP\n"); emitline("\tMOVQ\t$0, (SP)\n"); let ll: str = mklabel(c, "spr_l"); @@ -10444,9 +10534,7 @@ fn cgcall(c: *cgen, n: *node) void = { emitline("(BP)\n"); } else { cgexpr(c, aa2); - let op: str = "MOVQ"; - if (esz == 1) { op = "MOVB"; } - else { if (esz == 4) { op = "MOVL"; }; }; + let op: str = tnodestoreop(c, varp.lhs, esz); emitline("\t"); emitline(op); emitline("\tAX, "); @@ -10795,19 +10883,10 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn.kind == nkind.N_TPTR) { let pe: *node = tn.lhs; if (pe != nil) { - if (pe.kind == nkind.N_TNAME) { - let ps: i32 = primsize(pe.str); - if (ps == 1) { - loadop = "MOVZBQ"; - storeop = "MOVB"; - } else { if (ps == 4) { - if (typenameissigned(pe.str)) { - loadop = "MOVSXD"; - } else { - loadop = "MOVL"; - }; - storeop = "MOVL"; - }; }; + let ps: i32 = fieldsize(c, pe); + if (ps == 1 || ps == 2 || ps == 4) { + loadop = tnodeloadop(c, pe, ps); + storeop = tnodestoreop(c, pe, ps); }; }; }; @@ -11011,10 +11090,10 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { if (esz == 2) { emitline("\tMOVW\tAX, (BX)\n"); } - else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); };};}; + let isop: str = tnodestoreop(c, elemtn, esz); + emitline("\t"); + emitline(isop); + emitline("\tAX, (BX)\n"); return; }; }; @@ -11054,7 +11133,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); @@ -11110,7 +11189,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -11158,7 +11237,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); return; }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -11283,7 +11362,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\n"); return; }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -11298,7 +11377,7 @@ fn cgassign(c: *cgen, n: *node) void = { // → push → eval rhs → combine → // store. cgexpr clobbers BX, so // re-LEAQ for the store. - let lop: str = fieldloadop(fi); + let lop: str = fieldloadop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -11315,7 +11394,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); }; - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); @@ -11406,7 +11485,7 @@ fn cgassign(c: *cgen, n: *node) void = { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -11496,7 +11575,7 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - let sop: str = fieldstoreop(r.leaffi); + let sop: str = fieldstoreop(c, r.leaffi); cgexpr(c, n.rhs); if (r.isglobal) { emitline("\tLEAQ\t"); @@ -11609,7 +11688,7 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - let sop: str = fieldstoreop(ffi); + let sop: str = fieldstoreop(c, ffi); if (isptr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); @@ -12288,9 +12367,7 @@ fn cglet(c: *cgen, n: *node) void = { if (ps > 0) { esz = ps; }; }; }; - let mop: str = "MOVQ"; - if (esz == 1) { mop = "MOVB"; } - else { if (esz == 4) { mop = "MOVL"; }; }; + let mop: str = tnodestoreop(c, elemn, esz); let idx: i32 = 0; let repeat: bool = false; let e: *node = rhs.list; @@ -12406,7 +12483,7 @@ fn cglet(c: *cgen, n: *node) void = { emitline("(BP)\n"); fi = nil; } else { - let sop: str = fieldstoreop(fi); + let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); @@ -12721,16 +12798,11 @@ fn paramfieldsize(t: *node) i32 = { return 8; }; -// paramissigned — does this primitive type need sign-extending on a -// narrow (4B) load? Mirrors C cgen's `binds[b].signed_field` flag. -fn paramissigned(t: *node) bool = { - if (t == nil) { return false; }; - if (t.kind != nkind.N_TNAME) { return false; }; - let nm: str = t.str; - if (streq(nm, "i8")) { return true; }; - if (streq(nm, "i16")) { return true; }; - if (streq(nm, "i32")) { return true; }; - return false; +// paramissigned — does this type need sign-extending on a sub-word +// (1/2/4B) load? Mirrors cstage's signed_field check via +// fieldissignedc (resolves TBANG / TENUM / alias chains). +fn paramissigned(c: *cgen, t: *node) bool = { + return fieldissignedc(c, t); }; // cgforrange — lower `for (let x .. slice) body` (and the tuple- @@ -12804,7 +12876,7 @@ fn cgforrange(c: *cgen, n: *node) void = { let signf: bool = false; if (tp != nil) { fsz = paramfieldsize(tp); - signf = paramissigned(tp); + signf = paramissigned(c, tp); }; let slot_sz: i32 = fsz; if (slot_sz < 8) { slot_sz = 8; }; @@ -12832,7 +12904,7 @@ fn cgforrange(c: *cgen, n: *node) void = { // reads `u->sub->kind` for the elem type. bind_signed[0] = false; if (elemt != nil) { - bind_signed[0] = paramissigned(elemt); + bind_signed[0] = paramissigned(c, elemt); }; if (n.str.len > 0) { // Register with elem tnode so x.field on a loop @@ -12930,15 +13002,12 @@ fn cgforrange(c: *cgen, n: *node) void = { }; emitline("\tADDQ\tAX, BX\n"); - // Per-binding load from BX+foff. + // Per-binding load from BX+foff. Signedness comes from bind_signed + // (set via paramissigned → fieldissignedc), so enum-aliased narrows + // pick the right MOVS*Q without a literal-name gate. let b: i32 = 0; for (b < nbinds) { - let op: str = "MOVQ"; - if (bind_sz[b] == 1) { op = "MOVZBQ"; } - else { if (bind_sz[b] == 4) { - if (bind_signed[b]) { op = "MOVSXD"; } - else { op = "MOVL"; }; - };}; + let op: str = loadopsz(bind_signed[b], bind_sz[b]); emitline("\t"); emitline(op); emitline("\t"); diff --git a/test/wcc/660_field_signed.c b/test/wcc/660_field_signed.c new file mode 100644 index 00000000..e4e9d1e0 --- /dev/null +++ b/test/wcc/660_field_signed.c @@ -0,0 +1,267 @@ +/* + * 660_field_signed — sub-word signed struct/tuple/index field LOAD + * must sign-extend (MOVSBQ / MOVSWQ / MOVSXD), not zero-extend. + * Companion to 640 (N_CAST narrow) and 650 (chained N_DOT spine); + * pins task #10's field-load fix and task #5's TY_ENUM recursion + * through the predicate. Each fixture round-trips a high-bit-set + * value through a field, then widens back to i64 and compares. + * + * Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage) + * when present, so a regression on either side is caught here. + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want; }; + +static const struct row rows[] = { + /* i8 field LOAD: write -1 (0xFF), widen back; must read -1, not 255. */ + { "i8_field_load", + "type box = struct { v: i8, pad: i8 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = -1i64: i8; b.pad = 0i64: i8;\n" + " let z: i64 = b.v: i64;\n" + " if (z == -1i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* i16 field LOAD: write -32768 (0x8000), widen back; must read -32768. */ + { "i16_field_load", + "type box = struct { v: i16, pad: i16 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = -32768i64: i16; b.pad = 0i64: i16;\n" + " let z: i64 = b.v: i64;\n" + " if (z == -32768i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* i32 field LOAD: pins MOVSXD on the field-read path. */ + { "i32_field_load", + "type box = struct { v: i32, pad: i32 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = -2147483648i64: i32; b.pad = 0i32;\n" + " let z: i64 = b.v: i64;\n" + " if (z == -2147483648i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* u8 field LOAD with high bit: must zero-extend (regression check + * — the symmetric helper must NOT sign-extend u8). */ + { "u8_field_load", + "type box = struct { v: u8, pad: u8 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = 0xFFu64: u8; b.pad = 0u8;\n" + " let z: u64 = b.v: u64;\n" + " if (z == 0xFFu64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* u16 / u32 field LOAD: zero-extension on the field-read path. */ + { "u16_field_load", + "type box = struct { v: u16, pad: u16 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = 0xFFFFu64: u16; b.pad = 0u16;\n" + " let z: u64 = b.v: u64;\n" + " if (z == 0xFFFFu64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + { "u32_field_load", + "type box = struct { v: u32, pad: u32 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = 0xFFFFFFFFu64: u32; b.pad = 0u32;\n" + " let z: u64 = b.v: u64;\n" + " if (z == 0xFFFFFFFFu64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* bool field LOAD: zero-extends (0 or 1). The new helper must not + * sign-extend bool just because it's not in typenameisunsigned. */ + { "bool_field_load", + "type box = struct { v: bool, pad: bool };\n" + "fn main() i32 = {\n" + " let b: box; b.v = true; b.pad = false;\n" + " if (b.v) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Chained dotted load over an i8 leaf: spine walker must pick + * MOVSBQ at the terminal. Complements 650's i32/u32 leaf rows. */ + { "chained_i8_leaf_load", + "type inner = struct { v: i8, pad: i8 };\n" + "type outer = struct { i: inner, tag: i32 };\n" + "fn main() i32 = {\n" + " let o: outer; o.i.v = -1i64: i8; o.i.pad = 0i8; o.tag = 0;\n" + " let z: i64 = o.i.v: i64;\n" + " if (z == -1i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Chained i16 leaf — pins MOVSWQ through the spine walker. */ + { "chained_i16_leaf_load", + "type inner = struct { v: i16, pad: i16 };\n" + "type outer = struct { i: inner, tag: i32 };\n" + "fn main() i32 = {\n" + " let o: outer; o.i.v = -32768i64: i16; o.i.pad = 0i16; o.tag = 0;\n" + " let z: i64 = o.i.v: i64;\n" + " if (z == -32768i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Enum-aliased signed sub-word: `type myflag = i8` field. The + * principled predicate (task #5: TY_ENUM recursion in + * type_isunsigned, alias recursion in fieldissignedc) is what + * makes this fire MOVSBQ instead of MOVZBQ. */ + { "enum_signed_i8_alias_field_load", + "type myflag = i8;\n" + "type box = struct { v: myflag, pad: i8 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = -1i64: myflag; b.pad = 0i8;\n" + " let z: i64 = b.v: i64;\n" + " if (z == -1i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* N_CAST narrow on enum-aliased source. Without TY_ENUM recursion + * in type_isunsigned, the symmetric narrow gate misses this case + * and the cast is a silent no-op. */ + { "enum_alias_cast_narrow", + "type myflag = i8;\n" + "fn main() i32 = {\n" + " let x: i64 = 0xFF80i64;\n" + " let y: myflag = x: myflag;\n" + " let z: i64 = y: i64;\n" + " if (z == -128i64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Enum-aliased unsigned narrow: the symmetric helper must + * zero-extend an enum aliased to u8. */ + { "enum_unsigned_u8_alias_field_load", + "type byteflag = u8;\n" + "type box = struct { v: byteflag, pad: u8 };\n" + "fn main() i32 = {\n" + " let b: box; b.v = 0xFFu64: byteflag; b.pad = 0u8;\n" + " let z: u64 = b.v: u64;\n" + " if (z == 0xFFu64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* `*p OP= v` with *p:u32 and high bit set — pins the hidden + * bug at cgen.c N_ASSIGN deref-compound (was hardcoded MOVSXD + * for sz=4, sign-extending u32). Old path: load 0x80000001 → + * MOVSXD → 0xFFFFFFFF80000001, SHRQ 1 → 0x7FFFFFFFC0000000, + * MOVL store → 0xC0000000. New path: MOVL → 0x80000001, SHRQ 1 + * → 0x40000000 (the correct u32 logical right-shift result). */ + { "deref_compound_u32_high_bit", + "fn main() i32 = {\n" + " let x: u32 = 0x80000001u32;\n" + " let p: *u32 = &x;\n" + " *p >>= 1u32;\n" + " if (x == 0x40000000u32) { return 42; };\n" + " return 0;\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/wwfs_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfs_%d_d_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s", + 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; +} + +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]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + char wdrv[1024]; + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + struct { const char *name; const char *path; int gated_on_existence; } + drivers[] = { + { "cstage", cdrv, 0 }, + { "wwstage", wdrv, 1 }, + { NULL, NULL, 0 }, + }; + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + for (int d = 0; drivers[d].name; d++) { + if (drivers[d].gated_on_existence + && access(drivers[d].path, X_OK) != 0) { + fprintf(stderr, "field_signed: skip %s (no %s)\n", + drivers[d].name, drivers[d].path); + continue; + } + for (int i = 0; i < n; i++) { + int got = run_driver(drivers[d].path, &rows[i], i); + total++; + if (got != rows[i].want) { + fprintf(stderr, + "field_signed[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + } + if (fail) { + fprintf(stderr, + "field_signed: %d/%d fixtures failed\n", fail, total); + return 1; + } + printf("field_signed: %d/%d ok\n", total, total); + return 0; +}