/* * 724_letdecl_zeroinit — sentinel for STATUS-3 #22. Bare `let x: T;` * (no rhs) where T is `!void`, `void`, or a name-aliased type that * resolves to either — sz==8 but `typeis8byteprimitive` returns false * because there's no N_TBANG arm. Cstage at cmd/w6c/cgen.c:6381 emits * `MOVQ $0, -K(BP)` for any 8B slot unconditionally; wwstage skipped * the emit, leaving the slot uninit (stack residue). * * Filed STATUS-3 #22, previously byte-id drift only: 995_self_rebuild * masked the divergence because the bootstrap main.combined.ww had no * `let x: !void;` sites. utf8.next / utf8.utf8sz / utf8.validate * introduced them with lib/encoding/utf8 landing; lib/strings's * nested-if shapes (task #15) compound the drift past the per-rebuild * threshold — promotes #22 from latent to bootstrap-blocking. * * Fix aligns wwstage DOWN to cstage (rule 10) by extending * `typeis8byteprimitive` to recognise the same sz=8 default arms * cstage's N_LET falls through (N_TBANG recurses on inner, plain * `void`, alias chains via aliaslookup). Gating purely on sz==8 * would over-zero sub-8B structs whose wwstage slot pads to 8 — the * classifier-level fix is the narrow match. * * Rows pin asm-presence in both stages on the canonical shape and * cmp -s byte-id between them. */ #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; }; static const struct row rows[] = { /* `!void`-aliased let-decl — the utf8.invalid shape. */ { "bang_void_letdecl", "type invalid = !void;\n" "export fn produce() (i32 | invalid) = {\n" " let e: invalid;\n" " return e;\n" "};\n" }, /* Plain-`void`-aliased let-decl — the utf8.done / utf8.more shape. */ { "void_alias_letdecl", "type done = void;\n" "export fn produce() (i32 | done) = {\n" " let d: done;\n" " return d;\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; } /* The let-decl's slot is the first local off BP; cstage emits * `MOVQ $0, -8(BP)` (or the appropriate slot offset). We just look * for the literal `MOVQ\t$0, -` substring inside the produce body * (between the function label and the RET). */ static int check_movq_zero_present(const char *spath, const struct row *r) { char buf[1 << 16]; if (slurp(spath, buf, sizeof buf) < 0) return -1; const char *body = strstr(buf, "TEXT produce"); if (!body) { fprintf(stderr, "row[%s]: no TEXT produce label\n", r->label); return -1; } const char *end = strstr(body, "RET"); if (!end) end = buf + strlen(buf); long bodylen = end - body; if (bodylen <= 0 || (size_t)bodylen >= sizeof buf) return -1; char windowed[1 << 16]; memcpy(windowed, body, bodylen); windowed[bodylen] = '\0'; if (strstr(windowed, "MOVQ\t$0, -") == NULL) { fprintf(stderr, "row[%s]: no `MOVQ $0, -K(BP)` zero-init in produce body\n", r->label); return -1; } return 0; } 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/lzi_asm_%d_%d.ww", getpid(), i); snprintf(out_s, cap, "/tmp/lzi_asm_%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; } 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, "letdecl_zeroinit[cstage][%s]: w6c failed\n", rows[i].label); fail++; total++; continue; } total++; if (check_movq_zero_present(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, "letdecl_zeroinit[wwstage][%s]: w6c_ww failed\n", rows[i].label); fail++; total++; unlink(cs_path); continue; } total++; if (check_movq_zero_present(ws_path, &rows[i]) != 0) { fail++; } /* Byte-id between stages — the actual fix-pin. */ total++; char cmd[512]; snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); if (runwait(cmd) != 0) { fprintf(stderr, "letdecl_zeroinit[%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } unlink(cs_path); unlink(ws_path); } if (fail) { fprintf(stderr, "letdecl_zeroinit: %d/%d fixtures failed\n", fail, total); return 1; } printf("letdecl_zeroinit: %d/%d ok\n", total, total); return 0; }