From 45e5d74047625bc369e6d882574e8624b7f8037e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 30 May 2026 09:04:33 +0900 Subject: [PATCH] w6c+wwstage: tag a bare value widened into a NAMED-alias union variant via structural fallback (#15) Variant selection (cg_tag_for_variant / flatvariantidxt) matched union variants by exact type only, so widening a bare value (e.g. *vtable) into a union with a NAMED ptr-alias variant (stream = *vtable, in handle = (file | stream)) found no match and the tag defaulted to 0 -- the wrong variant. In the compiler this hit emitbytes' io.write(&cgoutstream.vt) once io.write took a handle, writing the asm to a garbage fd -> empty .s -> w6c_ww miscompiled everything. Add a second selection pass: when the exact pass finds no variant, structurally compare the bare source against each NAMED-alias variant's unwrapped type; exact-match still wins in pass 1 (so a bare i64 stays the i64 variant, not oserror=!i64, which kept the os/errno union building). A >=2-structural-match collision guard (extending #218's) hard-errors LOUDLY on genuine nominal ambiguity (two ptr-aliases to the same struct) instead of silently first-picking, citing #199b/#10. Symmetric across cstage (cmd/w6c/cgen.c) and wwstage (selfhost/cmd/wcc/cgenutil.ww). One-level NAMED unwrap (chained ptr-aliases unmatched, unexercised -> #17). Adds test/wcc/789 (positive widen byte-id+runtime + degenerate-ambiguity reject guard, both stages). Unblocks post-eFinal #5's handle surface. rule-10 fix-up. --- Makefile | 11 + cmd/w6c/cgen.c | 36 +++ selfhost/cmd/w6c/main.combined.ww | 39 +++ selfhost/cmd/wcc/cgenutil.ww | 39 +++ selfhost/cmd/wwdump/main.combined.ww | 39 +++ test/wcc/789_named_ptr_alias_variant_widen.c | 289 +++++++++++++++++++ 6 files changed, 453 insertions(+) create mode 100644 test/wcc/789_named_ptr_alias_variant_widen.c diff --git a/Makefile b/Makefile index 6c17b6ff..d2498dbb 100644 --- a/Makefile +++ b/Makefile @@ -337,6 +337,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_amp_fn_assign_run \ $(BIN)/test_xmod_alias_struct_collide_run \ $(BIN)/test_xmod_variant_match \ + $(BIN)/test_named_ptr_alias_variant_widen \ $(BIN)/test_structvariant_largeunion_return \ $(BIN)/test_narrow_alias_deref_store \ $(BIN)/test_bufio_vstream_run \ @@ -755,6 +756,16 @@ $(BIN)/test_xmod_variant_match: test/wcc/787_xmod_variant_match.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# #15: widening a bare *vtable into a NAMED-alias variant (`stream` = +# *vtable) of `(file | stream)` must compute the right tag, not default +# to tag 0. Both-stage byte-id + runtime, plus a degenerate-ambiguity +# reject row (drew's >=2 guard). Self-contained single-file probes. +$(BIN)/test_named_ptr_alias_variant_widen: test/wcc/789_named_ptr_alias_variant_widen.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # #9: returning a STRUCT variant of a LARGE (>4-eightbyte) tagged union. # Both-stage byte-id + runtime (reads tag AND the widened &fn field, so a # dropped-store regression can't hide behind self-consistent byte-id). diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index ad54d9f4..b80e068c 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -661,10 +661,46 @@ cg_tag_for_variant(Type *t, Type *vt) if (t == NULL || vt == NULL) return -1; if (t->kind == TY_NAMED) t = t->under; if (t == NULL || t->kind != TY_TAGGED) return -1; + /* Pass 1: exact match (NAMED-vs-NAMED pointer-id, tagged-vs-tagged, + * bare type_eq). Exact matches take precedence and need no guard — + * distinct variants don't exact-match the same source. */ int idx = 0; for (Tparam *p = t->params; p; p = p->next, idx++) { if (cg_variant_match(p->type, vt)) return idx; } + /* Pass 2 (#15): no exact variant matched — try a structural match of + * a BARE source against a NAMED-alias variant (e.g. a bare `*vtable` + * into the `stream` (= *vtable) variant of `(file | stream)`). The + * bare side has no nominal identity, so structure is the only + * discriminator; without this the widen found no variant and + * defaulted to tag 0, miscompiling every io.write(&...vt) in cgen's + * emit path. Exact-first (pass 1) keeps a bare `i64` into + * `(i64 | oserror)` binding the exact `i64`, not the alias. drew's + * proviso: guard the structural fallback like the #218 nested-widen + * site — if a bare source structurally matches >=2 NAMED variants, + * nominal layout is needed to disambiguate, so hard-error rather + * than silently first-pick. */ + if (vt->kind != TY_NAMED) { + int found = -1, n = 0; + idx = 0; + for (Tparam *p = t->params; p; p = p->next, idx++) { + /* One-level NAMED unwrap: a chained ptr-alias variant + * (type a=*X; type b=a) isn't reached here, so it would + * silently mis-tag — unexercised (zero in corpus), see + * task #17. */ + Type *pu = p->type; + if (pu && pu->kind == TY_NAMED && pu->under + && type_eq(pu->under, vt)) { + if (found < 0) found = idx; + n++; + } + } + if (n >= 2) + fatal("cg_tag_for_variant: bare source structurally " + "matches >=2 NAMED variants — ambiguous without " + "nominal layout (#15/#218/#199b/#10)"); + return found; + } return -1; } diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index f8fe69f0..a519c735 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -17010,6 +17010,9 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; if (ti == nil) { return -1; }; if (ti.kind != tykind.TY_TAGGED) { return -1; }; + // Pass 1: exact match (NAMED-vs-NAMED typeeq, tagged-vs-tagged, bare + // typeeq). Exact matches take precedence and need no guard — distinct + // variants don't exact-match the same source. let p: *tparam = ti.params; let idx: i32 = 0; for (p != nil) { @@ -17017,6 +17020,42 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { p = p.tnext; idx += 1; }; + // Pass 2 (#15): no exact variant matched — structurally match a BARE + // source against a NAMED-alias variant (bare *vtable into the + // `stream` (= *vtable) variant of `(file | stream)`). The bare side + // has no nominal identity, so structure is the only discriminator; + // without this the widen found no variant and defaulted to tag 0, + // miscompiling emitbytes' io.write(&cgoutstream.vt). Exact-first + // (pass 1) keeps a bare `i64` into `(i64 | oserror)` binding the + // exact `i64`. drew's proviso: guard the structural fallback like the + // #218 nested-widen site — a bare source matching >=2 NAMED variants + // needs nominal layout to disambiguate, so hard-error. + if (want.kind != tykind.TY_NAMED) { + let q: *tparam = ti.params; + let qi: i32 = 0; + let found: i32 = -1; + let n: i32 = 0; + for (q != nil) { + // One-level NAMED unwrap: a chained ptr-alias variant + // (type a=*X; type b=a) isn't reached here, so it would + // silently mis-tag — unexercised (zero in corpus), see + // task #17. + let pu: *tinfo = q.type_; + if (pu != nil && pu.kind == tykind.TY_NAMED + && pu.under != nil && typeeq(pu.under, want)) { + if (found < 0) { found = qi; }; + n += 1; + }; + q = q.tnext; + qi += 1; + }; + if (n >= 2) { + let msg: str = "flatvariantidxt: bare source structurally matches >=2 NAMED variants — ambiguous without nominal layout (#15/#218/#199b/#10)\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + return found; + }; return -1; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 23260df2..b460de4f 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -2253,6 +2253,9 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; if (ti == nil) { return -1; }; if (ti.kind != tykind.TY_TAGGED) { return -1; }; + // Pass 1: exact match (NAMED-vs-NAMED typeeq, tagged-vs-tagged, bare + // typeeq). Exact matches take precedence and need no guard — distinct + // variants don't exact-match the same source. let p: *tparam = ti.params; let idx: i32 = 0; for (p != nil) { @@ -2260,6 +2263,42 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { p = p.tnext; idx += 1; }; + // Pass 2 (#15): no exact variant matched — structurally match a BARE + // source against a NAMED-alias variant (bare *vtable into the + // `stream` (= *vtable) variant of `(file | stream)`). The bare side + // has no nominal identity, so structure is the only discriminator; + // without this the widen found no variant and defaulted to tag 0, + // miscompiling emitbytes' io.write(&cgoutstream.vt). Exact-first + // (pass 1) keeps a bare `i64` into `(i64 | oserror)` binding the + // exact `i64`. drew's proviso: guard the structural fallback like the + // #218 nested-widen site — a bare source matching >=2 NAMED variants + // needs nominal layout to disambiguate, so hard-error. + if (want.kind != tykind.TY_NAMED) { + let q: *tparam = ti.params; + let qi: i32 = 0; + let found: i32 = -1; + let n: i32 = 0; + for (q != nil) { + // One-level NAMED unwrap: a chained ptr-alias variant + // (type a=*X; type b=a) isn't reached here, so it would + // silently mis-tag — unexercised (zero in corpus), see + // task #17. + let pu: *tinfo = q.type_; + if (pu != nil && pu.kind == tykind.TY_NAMED + && pu.under != nil && typeeq(pu.under, want)) { + if (found < 0) { found = qi; }; + n += 1; + }; + q = q.tnext; + qi += 1; + }; + if (n >= 2) { + let msg: str = "flatvariantidxt: bare source structurally matches >=2 NAMED variants — ambiguous without nominal layout (#15/#218/#199b/#10)\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + return found; + }; return -1; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e4612eab..3eeb2a15 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -17010,6 +17010,9 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; if (ti == nil) { return -1; }; if (ti.kind != tykind.TY_TAGGED) { return -1; }; + // Pass 1: exact match (NAMED-vs-NAMED typeeq, tagged-vs-tagged, bare + // typeeq). Exact matches take precedence and need no guard — distinct + // variants don't exact-match the same source. let p: *tparam = ti.params; let idx: i32 = 0; for (p != nil) { @@ -17017,6 +17020,42 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { p = p.tnext; idx += 1; }; + // Pass 2 (#15): no exact variant matched — structurally match a BARE + // source against a NAMED-alias variant (bare *vtable into the + // `stream` (= *vtable) variant of `(file | stream)`). The bare side + // has no nominal identity, so structure is the only discriminator; + // without this the widen found no variant and defaulted to tag 0, + // miscompiling emitbytes' io.write(&cgoutstream.vt). Exact-first + // (pass 1) keeps a bare `i64` into `(i64 | oserror)` binding the + // exact `i64`. drew's proviso: guard the structural fallback like the + // #218 nested-widen site — a bare source matching >=2 NAMED variants + // needs nominal layout to disambiguate, so hard-error. + if (want.kind != tykind.TY_NAMED) { + let q: *tparam = ti.params; + let qi: i32 = 0; + let found: i32 = -1; + let n: i32 = 0; + for (q != nil) { + // One-level NAMED unwrap: a chained ptr-alias variant + // (type a=*X; type b=a) isn't reached here, so it would + // silently mis-tag — unexercised (zero in corpus), see + // task #17. + let pu: *tinfo = q.type_; + if (pu != nil && pu.kind == tykind.TY_NAMED + && pu.under != nil && typeeq(pu.under, want)) { + if (found < 0) { found = qi; }; + n += 1; + }; + q = q.tnext; + qi += 1; + }; + if (n >= 2) { + let msg: str = "flatvariantidxt: bare source structurally matches >=2 NAMED variants — ambiguous without nominal layout (#15/#218/#199b/#10)\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + return found; + }; return -1; }; diff --git a/test/wcc/789_named_ptr_alias_variant_widen.c b/test/wcc/789_named_ptr_alias_variant_widen.c new file mode 100644 index 00000000..29ca1346 --- /dev/null +++ b/test/wcc/789_named_ptr_alias_variant_widen.c @@ -0,0 +1,289 @@ +/* + * 789_named_ptr_alias_variant_widen — project #15 close. Pins that + * widening a BARE pointer into a tagged-union variant that is a NAMED + * ALIAS of that pointer type computes the correct variant tag, on BOTH + * stages byte-identically (rule-10). + * + * THE BUG (cgen, BOTH stages): cg_variant_match (cmd/w6c/cgen.c) / + * cgvariantmatch (selfhost/cmd/wcc/cgenutil.ww), in the "exactly one + * side NAMED" branch, only matched TAGGED-vs-TAGGED and otherwise + * returned no-match. So widening a bare `*vtable` into the `stream` + * (= *vtable) variant of `handle = (file | stream)` matched NO variant + * → the tag-selection (cg_tag_for_variant / flatvariantidxt) defaulted + * to tag 0 (= file). The eFinal `io.write(&cgoutstream.vt, buf)` in the + * compiler's own emit path (cgen.ww:745, `&cgoutstream.vt` a bare + * *io.vtable widening into io.handle) then dispatched the fd arm, + * wrote asm to a garbage fd, and the w6c_ww it built miscompiled every + * program (#15 — a "scale" red herring; it is per-call deterministic). + * + * THE FIX (#15 + drew proviso): the tag-selection does exact-match + * first (unchanged); only when NO variant exact-matches does it + * structurally match a BARE source against a NAMED-alias variant. A + * NAMED source still binds its own NAMED variant (a bare `i64` into + * `(i64 | oserror)` picks the exact `i64`, not the oserror alias). The + * structural fallback is guarded the way #218 guards its nested-widen + * site: a bare source matching >=2 NAMED variants is ambiguous without + * nominal layout → LOUD hard-error, never a silent first-pick. + * + * row | shape | exit | byte-id + * -------------------+-----------------------------------------+------+-------- + * handle_widen | bare *vtable widened into the `stream` | 42 | cs==ww + * | (= *vtable) variant of (file|stream), | | + * | summed 10x via the stream match arm | | + * | (pre-fix took the file arm → 86) | | + * degenerate_ambig | (sa | sb), both = *vtable; a bare | both | (reject + * | *vtable widen matches BOTH NAMED |reject| net) + * | variants → guard must LOUD hard-error | | + * | on BOTH stages (drew proviso lock) | | + * + * GATE POLARITY: must stay GREEN. Red on handle_widen means the + * NAMED-ptr-alias-variant widen regressed (wrong tag); red on + * degenerate_ambig means the >=2 ambiguity guard stopped firing. + */ +#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; +} + +#define STAGE_CS 1 +#define STAGE_WW 2 + +struct row { + const char *label; + const char *src; + int want_exit; + int stage_mask; + int byte_id; + int expect_reject; /* 1 = BOTH stages must hard-error */ +}; + +static const struct row rows[] = { + /* POSITIVE byte-id + runtime: bare *vtable widened into the NAMED + * `stream` variant — the #15 fix. */ + { "handle_widen", + "package main;\n" + "type vtable = struct { x: i32 };\n" + "type stream = *vtable;\n" + "type file = i32;\n" + "type handle = (file | stream);\n" + "fn hwrite(h: handle, n: u64) u64 = {\n" + " match (h) {\n" + " case file => return 111u64;\n" + " case let s: stream => return n;\n" + " };\n" + "};\n" + "fn emit(v: *vtable, n: u64) u64 = { return hwrite(v, n); };\n" + "export fn main() i32 = {\n" + " let vt: vtable; vt.x = 0;\n" + " let sum: u64 = 0u64; let i: i32 = 0;\n" + " for (i < 10) { sum = sum + emit(&vt, 4u64); i += 1; };\n" + " if (sum == 40u64) { return 42; };\n" + " return (sum: i32);\n" + "};\n", + 42, STAGE_CS | STAGE_WW, 1, 0 }, + + /* GUARD lock (drew proviso): a bare *vtable widened into a union of + * TWO NAMED *vtable aliases is ambiguous → BOTH stages hard-error. */ + { "degenerate_ambig", + "package main;\n" + "type vtable = struct { x: i32 };\n" + "type sa = *vtable;\n" + "type sb = *vtable;\n" + "type h = (sa | sb);\n" + "fn w(x: h) i32 = { match (x) { case sa => return 1; case sb => return 2; }; };\n" + "fn emit(v: *vtable) i32 = { return w(v); };\n" + "export fn main() i32 = { let vt: vtable; vt.x = 0; return emit(&vt); };\n", + 0, STAGE_CS | STAGE_WW, 0, 1 }, +}; + +static void +cleanup_tmp(const char *tmpdir, const char *base) +{ + char p[1024]; + snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p); + snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); + snprintf(p, sizeof p, "%s/%s.s", 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); + rmdir(tmpdir); +} + +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 int +build_via_driver(const char *driver, const char *tmpdir, const char *src) +{ + char cmd[2048]; + snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build %s 2>/dev/null", + tmpdir, driver, src); + 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/npav_%d_d_%d", getpid(), seq); + snprintf(base, sizeof base, "main789"); + snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base); + mkdir(tmpdir, 0755); + if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; } + int rc; + if (build_via_driver(driver, tmpdir, src) == 0) { + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + rc = runwait(outbin); + } else { + rc = -1; + } + cleanup_tmp(tmpdir, base); + return rc; +} + +/* build_fails — 1 iff the driver build FAILS (used by expect_reject). */ +static int +build_fails(const char *driver, const struct row *r, int seq) +{ + char tmpdir[256], src[512], base[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/npav_%d_x_%d", getpid(), seq); + snprintf(base, sizeof base, "main789"); + snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base); + mkdir(tmpdir, 0755); + if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return 0; } + int br = build_via_driver(driver, tmpdir, src); + cleanup_tmp(tmpdir, base); + return br != 0; +} + +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/npav_%d_c_%d", getpid(), seq); + snprintf(tdw, sizeof tdw, "/tmp/npav_%d_w_%d", getpid(), seq); + snprintf(base, sizeof base, "main789"); + mkdir(tdc, 0755); + mkdir(tdw, 0755); + snprintf(src, sizeof src, "%s/%s.ww", tdc, base); + 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.s", tdc, base); + snprintf(src, sizeof src, "%s/%s.ww", tdw, base); + 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.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 cwd[256]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + char absbin[512]; + if (bin[0] != '/') { + 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, seq = 0; + int wwpresent = (access(wdrv, X_OK) == 0); + + for (int i = 0; i < n; i++) { + const struct row *r = &rows[i]; + + if (r->stage_mask & STAGE_CS) { + total++; + if (r->expect_reject) { + if (!build_fails(cdrv, r, seq++)) { + fprintf(stderr, "npav[cs][%s]: built but expected reject\n", + r->label); + fail++; + } + } else { + int got = run_row(cdrv, r, seq++); + if (got != r->want_exit) { + fprintf(stderr, "npav[cs][%s]: exit=%d want=%d\n", + r->label, got, r->want_exit); + fail++; + } + } + } + + if (wwpresent && (r->stage_mask & STAGE_WW)) { + total++; + if (r->expect_reject) { + if (!build_fails(wdrv, r, seq++)) { + fprintf(stderr, "npav[ww][%s]: built but expected reject\n", + r->label); + fail++; + } + } else { + int got = run_row(wdrv, r, seq++); + if (got != r->want_exit) { + fprintf(stderr, "npav[ww][%s]: exit=%d want=%d\n", + r->label, got, r->want_exit); + fail++; + } + if (r->byte_id) { + total++; + if (asm_byte_identical(cdrv, wdrv, r, seq++) != 0) { + fprintf(stderr, "npav[byte-id][%s]: cstage vs wwstage asm differs\n", + r->label); + fail++; + } + } + } + } + } + + if (fail) { + fprintf(stderr, "named_ptr_alias_variant_widen: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("named_ptr_alias_variant_widen: %d/%d ok\n", total, total); + return 0; +}