/* * 949_void_error_singleton_run — #140: a `!void` error-singleton used * as a VALUE is a tag-only variant. Its void payload has size 0 and no * storage, so cgexpr must materialise NOTHING — only the enclosing * widen arm stamps the variant tag. * * Pre-fix cstage's cgexpr N_IDENT path fell through to the generic * global-value load and emitted `MOVQ main.(SB), AX` for a * payload symbol that is never defined: * w6l: undefined reference to 'main.too_long' * wwstage already emitted tag-only (no symbol load) — the runtime- * correct reference. The fix aligns cstage UP (ken-139 oracle). * * Every singleton-value position the cgexpr-ident root feeds is * covered (enumerated at HEAD — all four link-failed identically): * return, let-init, assign, call-arg. Rows also vary the union width * (8B scalar payload vs a >8B str payload, which exercises the * tag-return CX/R8 zeroing) and check BOTH the error-arm and the * success-arm tags so a mis-stamped tag flips the exit code. * * Three checks per row: * - byte-id: w6c vs w6c_ww .s must be identical (rule 10). * - no-bogus-load: the cstage .s must NOT reference `(SB)` * for the singleton type-name (the pre-fix undefined symbol). * - runtime: build via the ww / ww_ww drivers (the build step is * the link, which is the pre-fix teeth) and run; the exit code * pins the variant tag. * * This is half of the path-c1 pin (union with #141's def-dim file). */ #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; /* expected exit code */ }; static const struct row rows[] = { /* return-position: the void singleton returned from an 8B-payload * union; match selects the singleton arm (exit 0). */ { "return_singleton", "type too_long = !void;\n" "fn f() (i64 | too_long) = {\n" " return too_long;\n" "};\n" "export fn main() i32 = {\n" " match (f()) {\n" " case let v: i64 => return 1;\n" " case too_long => return 0;\n" " };\n" " return 2;\n" "};\n", 0 }, /* same fn shape, success variant: pins the tag discriminates * (the success arm must run, returning the payload). */ { "return_success", "type too_long = !void;\n" "fn f() (i64 | too_long) = {\n" " return 42i64;\n" "};\n" "export fn main() i32 = {\n" " match (f()) {\n" " case let v: i64 => { if (v != 42) { return 3; }; return 0; };\n" " case too_long => return 1;\n" " };\n" " return 2;\n" "};\n", 0 }, /* let-init: `let e: (i64 | too_long) = too_long;`. */ { "letinit_singleton", "type too_long = !void;\n" "export fn main() i32 = {\n" " let e: (i64 | too_long) = too_long;\n" " match (e) {\n" " case let v: i64 => return 1;\n" " case too_long => return 0;\n" " };\n" " return 2;\n" "};\n", 0 }, /* assign: reassign an existing union local to the void singleton * (cg_widen_tagged_store source = the singleton ident). */ { "assign_singleton", "type too_long = !void;\n" "export fn main() i32 = {\n" " let e: (i64 | too_long) = 5i64;\n" " e = too_long;\n" " match (e) {\n" " case let v: i64 => return 1;\n" " case too_long => return 0;\n" " };\n" " return 2;\n" "};\n", 0 }, /* call-arg: the singleton passed into a tagged parameter. */ { "callarg_singleton", "type too_long = !void;\n" "fn use(r: (i64 | too_long)) i32 = {\n" " match (r) {\n" " case let v: i64 => return 1;\n" " case too_long => return 0;\n" " };\n" " return 2;\n" "};\n" "export fn main() i32 = {\n" " return use(too_long);\n" "};\n", 0 }, /* wide union: a >8B (str) success payload makes the tagged return * slot exceed one word, exercising the CX/R8 zeroing on the * tag-only singleton return. Error arm selected. */ { "return_singleton_wide", "type too_long = !void;\n" "fn f(ok: bool) (str | too_long) = {\n" " if (ok) { return \"hello\"; };\n" " return too_long;\n" "};\n" "export fn main() i32 = {\n" " match (f(false)) {\n" " case let s: str => return 1;\n" " case too_long => { };\n" " };\n" " match (f(true)) {\n" " case let s: str => { if (s.len != 5) { return 2; }; };\n" " case too_long => return 3;\n" " };\n" " return 0;\n" "};\n", 0 }, }; static const char *g_bin; static int compile_s(const char *tool, const char *src, const char *outpath, const char *errpath) { char cmd[1024]; snprintf(cmd, sizeof cmd, "%s/%s %s > %s 2> %s", g_bin, tool, src, outpath, errpath); return runwait(cmd); } static int file_eq(const char *a, const char *b) { char cmd[1024]; snprintf(cmd, sizeof cmd, "cmp -s %s %s", a, b); return runwait(cmd) == 0; } static int file_has(const char *path, const char *needle) { FILE *f = fopen(path, "rb"); if (!f) return 0; static char buf[1 << 20]; size_t n = fread(buf, 1, sizeof buf - 1, f); fclose(f); buf[n] = '\0'; return strstr(buf, needle) != NULL; } static int run_driver(const char *driver, const char *src, const char *label) { char tmpdir[128], cmd[1024]; snprintf(tmpdir, sizeof tmpdir, "/tmp/tves_%d_d", getpid()); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s/%s build %s >/dev/null 2>&1", tmpdir, g_bin, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", label, driver); return -1; } const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[256]; 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(outbin); rmdir(tmpdir); return got; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; static 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; } g_bin = bin; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { const struct row *r = &rows[i]; char src[128], cs_s[128], ww_s[128]; char cs_e[128], ww_e[128]; snprintf(src, sizeof src, "/tmp/tves_%d_%d.ww", getpid(), i); snprintf(cs_s, sizeof cs_s, "/tmp/tves_%d_%d_cs.s", getpid(), i); snprintf(ww_s, sizeof ww_s, "/tmp/tves_%d_%d_ww.s", getpid(), i); snprintf(cs_e, sizeof cs_e, "/tmp/tves_%d_%d_cs.err", getpid(), i); snprintf(ww_e, sizeof ww_e, "/tmp/tves_%d_%d_ww.err", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return 1; fputs("package main;\n\n", f); fputs(r->src, f); fclose(f); total++; int cs_rc = compile_s("w6c", src, cs_s, cs_e); int ww_rc = compile_s("w6c_ww", src, ww_s, ww_e); if (cs_rc != 0 || ww_rc != 0) { fprintf(stderr, "FAIL row[%s]: compile rc cs=%d " "ww=%d\n", r->label, cs_rc, ww_rc); fail++; unlink(src); unlink(cs_s); unlink(ww_s); unlink(cs_e); unlink(ww_e); continue; } if (!file_eq(cs_s, ww_s)) { fprintf(stderr, "FAIL row[%s]: cs != ww .s\n", r->label); fail++; } /* the pre-fix undefined-symbol signature: a `(SB)` load of * the singleton type-name. Both unions name the singleton * `too_long`; the wide row also has it. No row legitimately * loads a `too_long(SB)` value (it is a type, not a global). */ if (file_has(cs_s, "too_long(SB)")) { fprintf(stderr, "FAIL row[%s]: cstage still loads " "the nonexistent void-singleton payload symbol\n", r->label); fail++; } int got_cs = run_driver("ww", src, r->label); if (got_cs != r->want) { fprintf(stderr, "FAIL row[%s] cstage: want %d " "got %d\n", r->label, r->want, got_cs); fail++; } char wwdrv[600]; snprintf(wwdrv, sizeof wwdrv, "%s/ww_ww", g_bin); if (access(wwdrv, X_OK) == 0) { int got_ww = run_driver("ww_ww", src, r->label); if (got_ww != r->want) { fprintf(stderr, "FAIL row[%s] wwstage: " "want %d got %d\n", r->label, r->want, got_ww); fail++; } } unlink(src); unlink(cs_s); unlink(ww_s); unlink(cs_e); unlink(ww_e); } if (fail) { fprintf(stderr, "void_error_singleton_run: %d/%d rows " "failed\n", fail, total); return 1; } printf("void_error_singleton_run: %d rows ok\n", total); return 0; }