/* * 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; }