/* * 930_sret_narrow_field_run — Class B semantic round-trip for the * sret callee's narrow trailing-field copy (task #33). End-to-end * pins that values cross the >24B struct-return boundary intact * when the struct ends in a narrow primitive (bool / u8 / i16 / * i32), including a mixed-width struct that interleaves several * narrow fields after the 24B slice payload. * * Pre-fix cstage's N_IDENT word-copy tail used slot-padded * `rt->size` and emitted MOVQ for the narrow trailing field — * the narrow source slot was BP-relative low and bordered the * @sretarg save (8B), so the 8-byte read at offset 32 swept in * the saved RDI byte and either wrote garbage past the bool / * misaligned the next stack slot at the callee's frame edge. * * Class A asm-presence is pinned at 730; this file catches * shared miscompiles that asm byte-id can't see. */ #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[] = { /* Trailing bool — the original surfacing case (lib/strings * iterator shape). Pre-fix the bool round-tripped to a * neighbour byte (the @sretarg low byte at -8(BP)), which on * the local stack frame was 0x01 (the saved RDI pointer low * byte). Test sets r=true and confirms the value survives. */ { "bool_true", "type t = struct { a: i32, s: []u8, r: bool };\n" "fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 7; v.r = true;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.a != 7) { return 1; };\n" " if (!x.r) { return 2; };\n" " return 0;\n" "};\n", 0 }, { "bool_false", "type t = struct { a: i32, s: []u8, r: bool };\n" "fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 9; v.r = false;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.a != 9) { return 1; };\n" " if (x.r) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* Trailing u8 with a high bit set. Pre-fix MOVQ-load at the * source slot's offset would clobber the high 7 bytes of the * read into AX with whatever lived above the byte slot. */ { "u8_high_bit", "type t = struct { a: i32, s: []u8, r: u8 };\n" "fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = 0xFFu8;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r != 0xFFu8) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* Trailing i16 with a negative bit pattern: the MOVW path * is exercised, and the receive site's sign extension must * read only 2 bytes. */ { "i16_negative", "type t = struct { a: i32, s: []u8, r: i16 };\n" "fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = -123i16;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r != -123i16) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* Trailing i32 negative: exercises the MOVL-tail path of the * fixed loop (k+4<=sz arm; pre-fix the k+8<=40 arm absorbed * this offset too). */ { "i32_negative", "type t = struct { a: i32, s: []u8, r: i32 };\n" "fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = -424242;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r != -424242) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* Mixed-width narrows interleaved after the 24B slice: bool + * i32 + i64 mix into one struct. All three trailing fields * survive intact only if each emits its declared width per * field's natural offset/size. */ { "mixed_narrow_after_slice", "type t = struct { s: []u8, r: bool, n: i32, k: i64 };\n" "fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.r = true; v.n = 1234567; v.k = 9876543210i64;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (!x.r) { return 1; };\n" " if (x.n != 1234567) { return 2; };\n" " if (x.k != 9876543210i64) { return 3; };\n" " return 0;\n" "};\n", 0 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[96], tmpdir[96], cmd[1024]; snprintf(src, sizeof src, "/tmp/sret_nr_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/sret_nr_%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[160]; 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[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[640]; 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, "sret_narrow_field_run: 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, "sret_narrow_field_run[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "sret_narrow_field_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("sret_narrow_field_run: %d/%d ok\n", total, total); return 0; }