/* * 730_sret_narrow_field — Class A asm-presence + byte-id for the * sret callee's N_IDENT word-copy tail when the returned struct * ends in a narrow primitive (bool / u8 / i8 / i16 / i32). * * Task #33. Cstage pre-fix used the slot-padded `rt->size` to drive * the field-copy loop's MOVQ/MOVL/MOVB tail; for a struct whose * natural size is unaligned (e.g. `{ i32, []u8, bool }` natural=33, * padded=40), the trailing MOVQ at offset 32 widened a 1-byte bool * into an 8-byte load+store — diverged from wwstage's correct MOVB * (which used natural size via `sretretsize` / `structnaturalsize`). * Corpus-coverage-blind: no in-tree stdlib struct had a bool field * at any offset until lib/strings.iterator landed (Hare's * `iterator { dec, reverse }` shape). * * Sentinel per row: in the `mk` body, between the final pre-RET * "RAX = @sretarg load" pair (the SysV return-the-pointer * discipline) and the first MOVQ word-store into (BX), assert * the last copy instruction targets the narrow MOV width derived * from the final field's type — NOT MOVQ. The trailing-field offset * is hard-coded per row (structurally fixed) and we pin both the * read-from-(BP) and store-to-(BX) sides. * * Plus byte-id between stages per row. */ #include #include #include #include #include #include "wwtestpkg.h" static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } /* `off` = byte offset of the trailing narrow field inside the * struct (= natural offset of that field). `mov` = expected mnemonic * for the trailing field-copy at that offset (MOVB / MOVW / MOVL). * Rows all share the shape `{ i32 a, []u8 s, NARROW r }` so a sits * at 0..4, s at 8..32, r at 32..32+sz(r). */ struct row { const char *label; const char *src; int off; const char *mov; }; static const struct row rows[] = { { "trailing_bool", "type t = struct { a: i32, s: []u8, r: bool };\n" "export fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = false;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r) { return 1; };\n" " return 0;\n" "};\n", 32, "MOVB" }, { "trailing_u8", "type t = struct { a: i32, s: []u8, r: u8 };\n" "export fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = 0u8;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r != 0u8) { return 1; };\n" " return 0;\n" "};\n", 32, "MOVB" }, /* Trailing i16: natural size 34. The chained loop has only * MOVQ/MOVL/MOVB arms (matching wwstage's identical chain), * so a 2-byte tail falls through to 2× MOVB at offsets 32, 33. * That's still narrow + byte-id; the bug-check is the negative * "no MOVQ at offset 32". Promoting both stages to a MOVW arm * is a separate refactor (preserves byte-id but out of #33). */ { "trailing_i16", "type t = struct { a: i32, s: []u8, r: i16 };\n" "export fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = 0i16;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r != 0i16) { return 1; };\n" " return 0;\n" "};\n", 32, "MOVB" }, { "trailing_i32", "type t = struct { a: i32, s: []u8, r: i32 };\n" "export fn mk() t = {\n" " let v: t; let z: []u8;\n" " v.s = z; v.a = 0; v.r = 0;\n" " return v;\n" "};\n" "export fn main() i32 = {\n" " let x: t = mk();\n" " if (x.r != 0) { return 1; };\n" " return 0;\n" "};\n", 32, "MOVL" }, }; static int emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) { char src[96], cmd[1024]; snprintf(src, sizeof src, "/tmp/sret_narrow_%d_%d.ww", getpid(), i); snprintf(out_s, cap, "/tmp/sret_narrow_%d_%d_%s.s", getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); FILE *f = fopen(src, "wb"); if (!f) return -1; wwtest_fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); int rc = runwait(cmd); unlink(src); return rc; } /* Asm-presence sentinel: inside mk's body (between `TEXT mk,` and * the first `RET`), the trailing field-copy at offset `r->off` must * use the narrow MOV mnemonic — both the read-from-(BP) and the * store-to-(BX) lines. Pre-fix the cstage emitted MOVQ at that * offset (8-byte over-wide) because the loop drove off slot-padded * size; wwstage emitted the narrow MOV via natural size. */ static int check_narrow_mov(const char *path, const struct row *r) { FILE *f = fopen(path, "rb"); if (!f) return -1; char line[1024]; int in_mk = 0; int saw_store = 0; int saw_load = 0; char store_needle[64], load_needle[64]; snprintf(store_needle, sizeof store_needle, "%s\tAX, %d(BX)\n", r->mov, r->off); snprintf(load_needle, sizeof load_needle, "%d(BP), AX", -16); /* not used as exact-match — see contains below */ (void)load_needle; while (fgets(line, sizeof line, f)) { if (!in_mk) { if (strstr(line, "TEXT main.mk,") || strstr(line, "TEXT\tmain.mk,")) in_mk = 1; continue; } if (strstr(line, "\tRET\n")) break; if (strstr(line, store_needle)) saw_store = 1; /* load: `\tMOV?\t-K(BP), AX\n` with the matching width. */ char loadkey[16]; snprintf(loadkey, sizeof loadkey, "%s\t", r->mov); if (strstr(line, loadkey) && strstr(line, "(BP), AX")) saw_load = 1; } fclose(f); if (!saw_store) { fprintf(stderr, "row[%s]: expected `%s AX, %d(BX)` in mk body\n", r->label, r->mov, r->off); return -1; } if (!saw_load) { fprintf(stderr, "row[%s]: expected narrow `%s -K(BP), AX` load in mk\n", r->label, r->mov); return -1; } return 0; } /* Negative sentinel: pre-fix bug pattern. cstage emitted * `MOVQ AX, off>(BX)` for the narrow trailing field — assert * absence in mk body. */ static int check_no_movq_at_off(const char *path, const struct row *r) { FILE *f = fopen(path, "rb"); if (!f) return -1; char line[1024]; int in_mk = 0; char needle[64]; snprintf(needle, sizeof needle, "MOVQ\tAX, %d(BX)\n", r->off); int bad = 0; while (fgets(line, sizeof line, f)) { if (!in_mk) { if (strstr(line, "TEXT main.mk,") || strstr(line, "TEXT\tmain.mk,")) in_mk = 1; continue; } if (strstr(line, "\tRET\n")) break; if (strstr(line, needle)) { bad = 1; break; } } fclose(f); if (bad) { fprintf(stderr, "row[%s]: unexpected `MOVQ AX, %d(BX)` (pre-#33" " over-wide pattern) in mk\n", r->label, r->off); return -1; } return 0; } 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 w6c[640], w6c_ww[640]; snprintf(w6c, sizeof w6c, "%s/w6c", bin); snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); int have_ww = (access(w6c_ww, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { char cs_path[128], ws_path[128]; if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); fail++; total++; continue; } total += 2; if (check_narrow_mov(cs_path, &rows[i]) != 0) fail++; if (check_no_movq_at_off(cs_path, &rows[i]) != 0) fail++; if (!have_ww) { unlink(cs_path); continue; } if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { fprintf(stderr, "row[%s]: w6c_ww failed\n", rows[i].label); fail++; total++; unlink(cs_path); continue; } total += 2; if (check_narrow_mov(ws_path, &rows[i]) != 0) fail++; if (check_no_movq_at_off(ws_path, &rows[i]) != 0) fail++; total++; char cmd[512]; snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } unlink(cs_path); unlink(ws_path); } if (fail) { fprintf(stderr, "sret_narrow_field: %d/%d fixtures failed\n", fail, total); return 1; } printf("sret_narrow_field: %d/%d ok\n", total, total); return 0; }