/* * 727_modcall_widen_slice — sentinel for #28. Pins wwstage's cgcall to * look up calleeparams for module-qualified `mod.fn(...)` callees, so * pushargsrev's widening detection fires for an N_IDENT slice arg * passed to a tagged-union parameter slot. * * Pre-fix wwstage `cgcall` (selfhost/cmd/wcc/cgenexpr.ww:2861) only * called fnparamslookup when `callee.kind == nkind.N_IDENT`. For * `mod.fn(...)` (N_DOT callee), `calleeparams` stayed nil; pushargsrev's * widening detection is gated on `param != nil` so it never fired; * the N_IDENT-slice fast path (cgenutil.ww:368-383) then pushed only * 3 slot words (ptr/len/cap) and DROPPED the variant tag word. The * callee subsequently dispatched on (callee-arg-reg holds ptr instead * of tag) — a runtime miscompile, not a pure byte-id divergence. * * Cstage finds params via `n->lhs->type` (the checker-set type on the * N_DOT callee node, cmd/w6c/cgen.c:4161-4165), bypassing the name- * driven registry. Wwstage needed the mirror via N_DOT.lhs.str. * * Same-family sister of #21 (N_CALL dispatch in pushargsrev), #19 * (N_TSLICE match arm), #24 (N_CALL slice arm) — the pattern is * wwstage dispatchers periodically missing arms cstage gets natively * via typed AST. * * Bootstrap-blocking when lib/strings v3 (drags utf8 + bytes into the * build) lands. */ #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; }; /* Two-module probe: `caller` calls `needle.want(h, n)` where `n: []u8` * widens into `(u8 | []u8)`. The // MODULE: directives + `use needle;` * shape parallels what cmd/ww driver synthesises in combined.ww. */ static const struct row rows[] = { { "dot_callee_slice_widen", "package needle;\n" "export fn want(haystack: []u8, needle: (u8 | []u8)) i32 = {\n" " let r: i32 = haystack.len;\n" " match (needle) {\n" " case let b: u8 => r = r + (b: i32);\n" " case let s: []u8 => r = r + s.len;\n" " };\n" " return r;\n" "};\n" "package caller;\n" "import needle;\n" "export fn main() i32 = {\n" " let h: []u8;\n" " let n: []u8 = h;\n" " return needle.want(h, n);\n" "};\n" }, }; 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/mws_%d_%d.ww", getpid(), i); snprintf(out_s, cap, "/tmp/mws_%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 TEXT main, the call to needle.want must push 4 needle words * (cap, len, ptr, tag) before the haystack pushes. Asserts the tag- * synth `MOVQ $1, AX; PUSHQ AX` lands between the slice-triple pushes * and the next-arg load. Pre-fix the tag synth is absent — the * needle slot ends at the third PUSHQ AX. */ static int check_tag_push_present(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, "TEXT main"); if (!fn) { fprintf(stderr, "row[%s][%s]: no TEXT main in %s\n", r->label, stage, spath); return -1; } const char *call = strstr(fn, "CALL\tneedle.want"); if (!call) { fprintf(stderr, "row[%s][%s]: no CALL needle.want\n", r->label, stage); return -1; } /* Tag synth: a `MOVQ\t$1, AX` followed by a `PUSHQ\tAX` between * fn-start and the call. The literal `$1` is the slice-variant * tag index in (u8 | []u8). */ int found = 0; const char *p = fn; while (p < call) { const char *m = strstr(p, "MOVQ\t$1, AX"); if (!m || m >= call) break; const char *nx = strstr(m, "PUSHQ\tAX"); if (nx && nx < call) { found = 1; break; } p = m + 1; } if (!found) { fprintf(stderr, "row[%s][%s]: no tag-synth `MOVQ $1, AX; PUSHQ AX` before CALL needle.want\n", r->label, stage); return -1; } /* Also assert the post-call `ADDQ $8, SP` (overflow cleanup) is * emitted — 7 SysV reg slots for {haystack 3 + needle 4} leaves * 1 word stack-overflowed. Pre-fix wwstage only pushed 6, so no * cleanup either. */ const char *cleanup = strstr(call, "ADDQ\t$8, SP"); const char *nexttext = strstr(call, "TEXT "); if (!cleanup || (nexttext && cleanup > nexttext)) { fprintf(stderr, "row[%s][%s]: no `ADDQ $8, SP` overflow cleanup after CALL needle.want\n", r->label, stage); 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, "modcall_widen_slice[cstage][%s]: w6c failed\n", rows[i].label); fail++; total++; continue; } total++; if (check_tag_push_present(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, "modcall_widen_slice[wwstage][%s]: w6c_ww failed\n", rows[i].label); fail++; total++; unlink(cs_path); continue; } total++; if (check_tag_push_present(ws_path, &rows[i], "wwstage") != 0) fail++; /* byte-id between stages — the principled sentinel for the * polarity catalog. */ total++; char cmd[512]; snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); if (runwait(cmd) != 0) { fprintf(stderr, "modcall_widen_slice[%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } unlink(cs_path); unlink(ws_path); } if (fail) { fprintf(stderr, "modcall_widen_slice: %d/%d fixtures failed\n", fail, total); return 1; } printf("modcall_widen_slice: %d/%d ok\n", total, total); return 0; }