Files
ww/test/wcc/771_widen_transitive.c
Hojun-Cho eb6083e54a test: retarget Pattern-B gates to sep .sepwork layout; 915 off private global (M4 E3, #93)
The driver flip moves build artifacts from next-to-source <stem>.s to a
.sepwork/ scratch dir. 29 Pattern-B gates now build `ww build --sep -o <stem>`
and read <stem>.sepwork/__root.s (multi-package gates concat all
<stem>.sepwork/*.s, since cross-package labels live in per-package .s).
All intermediates redirect to /tmp (WW_PKGCACHE + -o), so the corpus runs
parallel-safe with no source-tree pollution. Tests pass now (--sep is live)
and survive the flip.

915 additionally retargeted off strconv's PRIVATE left_shift_table (a let,
not export) — which separate compilation correctly hides — onto a test-local
package that exports its own probe table (#96). The combined path only linked
it via a single-unit private leak; encapsulation is now honored under sep.

Test-only; all 5 *_ww binaries HOLD. 989_m1mangle_run deferred (blocked by
#99, imported-package fn main mangling under sep).
2026-06-18 11:16:56 +09:00

321 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 771_widen_transitive — project #199 (α): cstage type_assignable +
* wwstage isassignable must REJECT transitive nested-tagged widen.
* Cgen has no wrapped-slot layout (#199b deferred future-work); the
* pre-α recursive `type_assignable(p->type, src)` walk admitted
* `let r: (size|io.eof|io.error) = u` for u: io.underread, then cgen
* silently miscompiled (cg_tag_for_variant returned -1 → tag=0 → r
* reads as variant 0 = size at runtime).
*
* Fix (cmd/wcc/type.c:308 + selfhost/cmd/wcc/check.ww:3063): the
* tagged-non-tagged arm now matches direct variants ONLY. NAMED-tagged
* wrapper variants (vu.kind == TY_TAGGED) compare nominally (type_eq /
* typeeqast); other variants keep the leaf recursion for primitive
* compat / untyped resolution. wwstage additionally preserves the
* spread-recursion path: `...wrapper` variants (op == TK_ELLIPSIS) drill
* through because the AST list hasn't pre-flattened (cstage flattens at
* resolve_type, wwstage stays AST-keyed).
*
* Restores SSoT inside the checker pair: `is` / `as` / `match` arm
* lookup is already non-recursive (#198 sibling), and the LET-init /
* return / assign arms now agree. Aligns DOWN to the leaner side
* (rule-10 stage symmetry). ww-stricter than Hare; harec keeps the
* drill at ref/harec/src/types.c:702-739 (#199b — wrapped-slot layout
* port deferred per drew).
*
* Coverage (5 rows):
* 1. reject_transitive_widen — `let r: (size|wrapper) = u` with
* u: underread, wrapper = !(underread | nomem). BOTH stages
* MUST reject at the checker. The repro.
* 2. spread_alt_widen — `let r: (size|...wrapper) = u;`. The
* `...` spread inlines wrapper's variants into the parent flat
* set, so underread becomes a DIRECT variant; widen succeeds.
* CS-only runtime: wwstage's `is`/`match` cross-spread variant
* lookup is open-bug #190/#198 (wwstage walks original AST list,
* doesn't see expanded variants); byte-id still gates the asm.
* 3. direct_flat_variant — `let r: (size|underread|nomem) = u`
* with the underlying variants flat in the parent (no wrapper).
* Regression gate: direct widen unchanged by the fix.
* 4. branched_callee_widen — fn returns (size|underread|nomem),
* runtime picks between two flat-variant returns. Verifies the
* cg_widen_tagged_store scalar branch's tag synthesis is
* unchanged for direct widens.
* 5. wrapper_typed_widen — `let r: wrapper = u` with u:
* underread (DIRECT variant of wrapper). Regression gate: the
* single-level wrapper widen is the supported escape hatch and
* MUST continue working.
*
* Per-row gates: cstage compile + runtime, wwstage compile + runtime,
* cs.s == ww.s byte-identical (rule-10). Row 1 uses
* expected_exit = -2 to indicate "build must fail" — the driver
* invocation is expected to return non-zero from the checker.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
#define STAGE_CS 1
#define STAGE_WW 2
#define EXPECT_REJECT (-2)
struct row {
const char *label;
const char *src;
int expected_exit; /* EXPECT_REJECT means build must fail */
int stage_mask;
int byte_id; /* run cs.s == ww.s diff (works rows only) */
};
static const struct row rows[] = {
{ "reject_transitive_widen",
"package main;\n"
"type underread = !i32;\n"
"type nomem = !void;\n"
"type wrapper = !(underread | nomem);\n"
"export fn main() i32 = {\n"
" let u: underread;\n"
" let r: (size | wrapper) = u;\n"
" return 0;\n"
"};\n",
EXPECT_REJECT,
STAGE_CS | STAGE_WW, 0 },
{ "spread_alt_widen",
"package main;\n"
"type underread = !i32;\n"
"type nomem = !void;\n"
"type wrapper = !(underread | nomem);\n"
"export fn main() i32 = {\n"
" let u: underread;\n"
" let r: (size | ...wrapper) = u;\n"
" if (r is underread) { return 70; };\n"
" return 99;\n"
"};\n",
70,
STAGE_CS, 0 },
{ "direct_flat_variant",
"package main;\n"
"type underread = !i32;\n"
"type nomem = !void;\n"
"export fn main() i32 = {\n"
" let u: underread;\n"
" let r: (size | underread | nomem) = u;\n"
" if (r is underread) { return 71; };\n"
" return 99;\n"
"};\n",
71,
STAGE_CS | STAGE_WW, 1 },
{ "branched_callee_widen",
"package main;\n"
"type underread = !i32;\n"
"type nomem = !void;\n"
"fn pick(b: bool) (size | underread | nomem) = {\n"
" if (b) { let u: underread; return u; };\n"
" return 42: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let r: (size | underread | nomem) = pick(true);\n"
" if (r is underread) { return 72; };\n"
" return 99;\n"
"};\n",
72,
STAGE_CS | STAGE_WW, 1 },
{ "wrapper_typed_widen",
"package main;\n"
"type underread = !i32;\n"
"type nomem = !void;\n"
"type wrapper = !(underread | nomem);\n"
"export fn main() i32 = {\n"
" let u: underread;\n"
" let r: wrapper = u;\n"
" if (r is underread) { return 73; };\n"
" return 99;\n"
"};\n",
73,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1024];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wt_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main771.ww", tmpdir);
snprintf(base, sizeof base, "main771");
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) {
cleanup_tmp(tmpdir, base);
return -1;
}
int rc;
int br = build_via_driver(driver, tmpdir, src);
if (r->expected_exit == EXPECT_REJECT) {
/* Build must fail with a non-zero exit (checker error). */
rc = (br != 0) ? r->expected_exit : 0;
} else if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/wt_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/wt_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main771");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main771.ww", tdc);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/main771.ww", tdw);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
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 cdrv[640], wdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"widen_transitive[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"widen_transitive[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"widen_transitive[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "widen_transitive: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "widen_transitive: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("widen_transitive: %d/%d ok\n", total, total);
return 0;
}