/* * 745_fnret34_modshadow — sentinel for task #34 (sub-bug of #4e): * wwstage's cgcall return-ABI fixup keyed bare-leaf fnretlookup, not * fnretlookupmod. For N_DOT cross-module callees the post-#4e same- * module-first walk grabbed the caller-module's same-leaf fn's * return type — when that caller-side fn returned `str` and the * cross-module callee returned a non-str ([]u8, struct, scalar) the * `isstrtype(c, rt)` guard fired against the wrong type and emitted * a spurious `MOVQ DX, BX` shuffle after the cross-module CALL * (selfhost/cmd/wcc/cgenexpr.ww cgcall return-ABI fixup). * * Sibling: nodeisslice / nodeisstr N_CALL arms were N_IDENT-only * (selfhost/cmd/wcc/cgenutil.ww), so pushargsrev's slice/str word- * count fell through to the 1-word natural-push for any cross- * module N_DOT call result — dropping `.len` (and `.cap` for * slices). Hare's `strings.slice` body * (`fromutf8_unsafe(utf8.slice(begin, end))`, ref/hare/strings/ * iter.ha:75-77) hits both bugs at once because strings.slice * itself returns str (str-shuffle phantom on the inner utf8.slice * call) AND passes the inner []u8 to a same-module fn (.len/.cap * dropped on the push). * * Cstage carries no sister bug: cmd/w6c/cgen.c reads the return * shape from the typed `n->lhs->type` (TY_FN sig), module-aware via * the typed AST. Both cgcall's str-shuffle and the slice/str-arg * push count come off n->type directly. Mirror of #4e: cstage * sidesteps every fnretlookup-keyed bare-leaf table. * * Pin: row 1 — caller-module exports `fn slice(s: str) str` (same * leaf as the cross-module callee, return shape diverges); calls * `myutf8.slice(&d)` returning []u8. Pre-fix wwstage emits a phantom * `MOVQ DX, BX` after the CALL (and drops the slice 3-word push if * the result is consumed as a slice arg). Post-fix the TEXT sym * for caller.run carries no `MOVQ DX, BX` between the CALL and the * RET. Asserts CALL myutf8.slice present + bad_imm anti-check on * each stage plus cs-vs-ws byte-id. */ #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; const char *textsym; /* TEXT sym containing the call */ const char *want_imm; /* substring that MUST appear */ const char *bad_imm; /* substring that MUST NOT appear */ }; /* caller.slice (same leaf as myutf8.slice, returns str) lives in the * same source so caller's fnret table holds a slice-named fn returning * str. caller.run calls the cross-module myutf8.slice (returning []u8). * Pre-fix wwstage's cgcall fnretlookup picks caller.slice (same-module- * first) and emits a phantom MOVQ DX, BX after the cross-module CALL. */ static const struct row rows[] = { { "cross_module_same_leaf_str_shuffle", "package caller;\n" "import myutf8;\n" "export fn main() i32 = { return 0; };\n" "export fn slice(s: str) str = { return s; };\n" "export fn run() i32 = {\n" "\tlet d: myutf8.decoder;\n" "\td.x = 0;\n" "\tlet s: []u8 = myutf8.slice(&d);\n" "\treturn s.len;\n" "};\n" "package myutf8;\n" "export type decoder = struct { x: i32 };\n" "export fn slice(d: *myutf8.decoder) []u8 = {\n" "\tlet r: []u8;\n" "\tr.ptr = nil: *u8;\n" "\tr.len = d.x;\n" "\tr.cap = d.x;\n" "\treturn r;\n" "};\n", "TEXT caller.run", "CALL\tmyutf8.slice", "MOVQ\tDX, BX" }, }; static int slurp(const char *path, char *buf, size_t cap) { FILE *f = fopen(path, "rb"); if (!f) return -1; size_t n = fread(buf, 1, cap - 1, f); fclose(f); buf[n] = '\0'; return (int)n; } static int emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) { char src[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/fr34_%d_%d.ww", getpid(), i); snprintf(out_s, cap, "/tmp/fr34_%d_%d_%s.s", getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); FILE *f = fopen(src, "wb"); if (!f) return -1; 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; } /* Inside the named TEXT sym, before its first RET, want_imm MUST * appear and bad_imm MUST NOT. bad_imm flags pre-fix wwstage str- * shuffle firing on a non-str cross-module callee. */ static int check_imm(const char *spath, const struct row *r, const char *stage) { char buf[1 << 14]; if (slurp(spath, buf, sizeof buf) < 0) { fprintf(stderr, "row[%s][%s]: cannot read %s\n", r->label, stage, spath); return -1; } const char *fn = strstr(buf, r->textsym); if (!fn) { fprintf(stderr, "row[%s][%s]: no %s in %s\n", r->label, stage, r->textsym, spath); return -1; } const char *ret = strstr(fn, "\tRET"); if (!ret) { fprintf(stderr, "row[%s][%s]: no RET inside %s\n", r->label, stage, r->textsym); return -1; } const char *good = strstr(fn, r->want_imm); if (!good || good >= ret) { fprintf(stderr, "row[%s][%s]: want_imm %s missing inside %s\n", r->label, stage, r->want_imm, r->textsym); return -1; } const char *bad = strstr(fn, r->bad_imm); if (bad && bad < ret) { fprintf(stderr, "row[%s][%s]: bad_imm %s present inside %s — wrong-module str-shuffle phantom fired\n", r->label, stage, r->bad_imm, r->textsym); 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, "fnret34_modshadow[cstage][%s]: w6c failed\n", rows[i].label); fail++; total++; continue; } total++; if (check_imm(cs_path, &rows[i], "cstage") != 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, "fnret34_modshadow[wwstage][%s]: w6c_ww failed\n", rows[i].label); fail++; total++; unlink(cs_path); continue; } total++; if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; total++; char cmd[512]; snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); if (runwait(cmd) != 0) { fprintf(stderr, "fnret34_modshadow[%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } unlink(cs_path); unlink(ws_path); } if (fail) { fprintf(stderr, "fnret34_modshadow: %d/%d fixtures failed\n", fail, total); return 1; } printf("fnret34_modshadow: %d/%d ok\n", total, total); return 0; }