wwstage: spill non-IDENT scrutinee at cgtypeassert (#200)
cgtypeassert kept scrutoff=0 when the scrutinee wasn't an N_IDENT (direct call result, arr[i], p.field, ?, paren-wrap of any of those), so the tag-load fell on (BP) — the saved-BP word — and the payload- load on +8(BP) — the return address. The wwstage repro returned 220 (garbage from RIP) where cstage returned 42 (impl-e1-resume sibling of #199/#201). Mirror cstage cmd/w6c/cgen.c:6300-6316 N_TYPEASSERT non-IDENT arm. Add an `else` branch after the existing N_IDENT path that resolves the tagged type via matchscrutt, alloc an @asrt_spill slot via matchspillsz/localalloc, cgexpr the LHS, then spill the AX/DX/CX tagged-return-ABI words: AX→+0 (tag), DX→+8 (word0), CX→+16 (word1, guarded on spill > 16). Subsequent tag-check + payload load indexes off the spill like the IDENT path. Helpers reused from cgmatch (cgenexpr.ww:1422-1460). cstage's cgtypeassert omits the cgmatch 4-word R8→+24 spill (rule-10 stage symmetry: rather than diverge into a 32B-payload case the test suite doesn't exercise, mirror cstage exactly and file the cstage omission inline). Filed inline: cstage cgtypeassert needs the same R8→+24 path cgmatch already has (drew's design rationale, blocked by the rule-10 floor today). 772_typeassert_nonident: 7 rows (call_as_size — the repro, call_as_str — CX→+16 spill + BX post-load, call_as_namedvoid — void-variant tag-check fires, payload load is a 0-byte no-op, call_as_fnptr — fn-ptr variant 8B word0, call_as_u8 / call_as_i16 — narrow scalar round-trip via MOVQ + MOVQ confirms no truncation, branched_call_as — runtime-chosen tag). Each row gated on cstage runtime + wwstage runtime + cs.s == ww.s byte-identity.
This commit is contained in:
7
Makefile
7
Makefile
@@ -322,6 +322,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_dot_aliased_ptr \
|
||||
$(BIN)/test_return_tagged_forward \
|
||||
$(BIN)/test_widen_transitive \
|
||||
$(BIN)/test_typeassert_nonident \
|
||||
$(BIN)/test_use_promote_alias \
|
||||
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
@@ -660,6 +661,12 @@ $(BIN)/test_widen_transitive: test/wcc/771_widen_transitive.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_typeassert_nonident: test/wcc/772_typeassert_nonident.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 $@ $<
|
||||
|
||||
$(BIN)/test_arrlit_str_full: test/wcc/711_arrlit_str_full.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -18010,6 +18010,41 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, arr[i], p.field, ?,
|
||||
// etc.). Mirror cgmatch's spill (cgenexpr.ww:1422-1460)
|
||||
// and cstage cmd/w6c/cgen.c:6300-6316: alloc an
|
||||
// `@asrt_spill` slot sized via matchspillsz, evaluate
|
||||
// the LHS, then copy the AX/DX/CX[/R8] return-ABI words
|
||||
// into the slot so the tag-check + payload load indexes
|
||||
// off memory like the IDENT path. Without this, scrutoff
|
||||
// stayed 0 and the tag read fell on (BP) — the saved-BP
|
||||
// word — and the payload read on +8(BP) — the return
|
||||
// address. Bug #200.
|
||||
scrutt = matchscrutt(c, lhs);
|
||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
|
||||
cgexpr(c, lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (!isnullabletype(scrutt)) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
// Mirror cstage cmd/w6c/cgen.c:6313-6315: only CX
|
||||
// → +16 when slot_size > 16. The 4-word case
|
||||
// (R8 → +24, slot_size > 24) is the cgmatch shape
|
||||
// (cgenexpr.ww:1454-1458, cmd/w6c/cgen.c:5988-5990)
|
||||
// but cstage cgtypeassert omits it; preserve the
|
||||
// asymmetry rather than diverge from rule 10
|
||||
// byte-id. Filed inline as a cstage twin task.
|
||||
if (spillsz > 16) {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
|
||||
@@ -387,6 +387,41 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, arr[i], p.field, ?,
|
||||
// etc.). Mirror cgmatch's spill (cgenexpr.ww:1422-1460)
|
||||
// and cstage cmd/w6c/cgen.c:6300-6316: alloc an
|
||||
// `@asrt_spill` slot sized via matchspillsz, evaluate
|
||||
// the LHS, then copy the AX/DX/CX[/R8] return-ABI words
|
||||
// into the slot so the tag-check + payload load indexes
|
||||
// off memory like the IDENT path. Without this, scrutoff
|
||||
// stayed 0 and the tag read fell on (BP) — the saved-BP
|
||||
// word — and the payload read on +8(BP) — the return
|
||||
// address. Bug #200.
|
||||
scrutt = matchscrutt(c, lhs);
|
||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
|
||||
cgexpr(c, lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (!isnullabletype(scrutt)) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
// Mirror cstage cmd/w6c/cgen.c:6313-6315: only CX
|
||||
// → +16 when slot_size > 16. The 4-word case
|
||||
// (R8 → +24, slot_size > 24) is the cgmatch shape
|
||||
// (cgenexpr.ww:1454-1458, cmd/w6c/cgen.c:5988-5990)
|
||||
// but cstage cgtypeassert omits it; preserve the
|
||||
// asymmetry rather than diverge from rule 10
|
||||
// byte-id. Filed inline as a cstage twin task.
|
||||
if (spillsz > 16) {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
|
||||
@@ -18010,6 +18010,41 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, arr[i], p.field, ?,
|
||||
// etc.). Mirror cgmatch's spill (cgenexpr.ww:1422-1460)
|
||||
// and cstage cmd/w6c/cgen.c:6300-6316: alloc an
|
||||
// `@asrt_spill` slot sized via matchspillsz, evaluate
|
||||
// the LHS, then copy the AX/DX/CX[/R8] return-ABI words
|
||||
// into the slot so the tag-check + payload load indexes
|
||||
// off memory like the IDENT path. Without this, scrutoff
|
||||
// stayed 0 and the tag read fell on (BP) — the saved-BP
|
||||
// word — and the payload read on +8(BP) — the return
|
||||
// address. Bug #200.
|
||||
scrutt = matchscrutt(c, lhs);
|
||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
|
||||
cgexpr(c, lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (!isnullabletype(scrutt)) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
// Mirror cstage cmd/w6c/cgen.c:6313-6315: only CX
|
||||
// → +16 when slot_size > 16. The 4-word case
|
||||
// (R8 → +24, slot_size > 24) is the cgmatch shape
|
||||
// (cgenexpr.ww:1454-1458, cmd/w6c/cgen.c:5988-5990)
|
||||
// but cstage cgtypeassert omits it; preserve the
|
||||
// asymmetry rather than diverge from rule 10
|
||||
// byte-id. Filed inline as a cstage twin task.
|
||||
if (spillsz > 16) {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
|
||||
332
test/wcc/772_typeassert_nonident.c
Normal file
332
test/wcc/772_typeassert_nonident.c
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* 772_typeassert_nonident — project #200: wwstage cgtypeassert non-IDENT
|
||||
* scrutinee bug. The pre-fix `e as T` on a non-N_IDENT scrutinee (direct
|
||||
* call result, arr[i], p.field, ?, paren-wrap of any of those) left
|
||||
* scrutoff=0; the tag-load fell on (BP) — the saved-BP word — and the
|
||||
* payload-load on +8(BP) — the return address — instead of the spilled
|
||||
* tagged-return ABI words. wwstage produced garbage (exit 220 in the
|
||||
* repro vs cstage's 42).
|
||||
*
|
||||
* Fix (selfhost/cmd/wcc/cgenexpr.ww:368-414 cgtypeassert): mirror cstage
|
||||
* cmd/w6c/cgen.c:6300-6316 N_TYPEASSERT non-IDENT arm. Add an `else`
|
||||
* branch after the existing N_IDENT path: resolve the tagged type via
|
||||
* matchscrutt, alloc an `@asrt_spill` slot via matchspillsz/localalloc,
|
||||
* cgexpr the LHS, then spill AX→+0 (tag), DX→+8 (word0), CX→+16
|
||||
* (word1) guarded on spill > 16. Mirrors cgmatch's spill (cgenexpr.ww:
|
||||
* 1422-1460). The R8→+24 path (cgmatch shape) is intentionally omitted
|
||||
* to mirror cstage's cgtypeassert exactly (rule-10 byte-id); the cstage
|
||||
* twin gap is filed inline as a sibling task.
|
||||
*
|
||||
* Coverage (7 rows):
|
||||
* 1. call_as_size — `f() as size`. The repro shape: direct call
|
||||
* result, basic-size payload. Pre-fix
|
||||
* wwstage: garbage AX from (BP). Post-fix:
|
||||
* 42.
|
||||
* 2. call_as_str — `f() as str`. Non-IDENT, multi-word payload
|
||||
* (AX=ptr / BX=len). Tests CX→+16 spill +
|
||||
* the `isstrtype` post-spill BX load.
|
||||
* 3. call_as_namedvoid — `f() as nvariant` where nvariant is a
|
||||
* named-void alias. Tag-check fires; the
|
||||
* payload load is a 0-byte no-op (void
|
||||
* variant has size 0; no consumer reads AX).
|
||||
* Drew-add.
|
||||
* 4. call_as_fnptr — `f() as *fn(i32) i32`. Fn-ptr variant
|
||||
* (8B word0 = pointer). Round-trips through
|
||||
* spill→tag-check→MOVQ +8 → AX.
|
||||
* 5. call_as_u8 — `f() as u8`. Narrow scalar. The callee
|
||||
* zero/sign-extended the value to 8B on
|
||||
* return; MOVQ store + MOVQ read round-trips
|
||||
* it. Ken-add: confirms no narrow-truncation
|
||||
* on the payload load.
|
||||
* 6. call_as_i16 — `f() as i16`. Narrow signed scalar. Tests
|
||||
* negative round-trip through the spill slot
|
||||
* (callee sign-extended to 8B; MOVQ
|
||||
* preserves). Ken-add sibling of row 5.
|
||||
* 7. branched_call_as — callee returns a runtime-chosen variant
|
||||
* (the success branch); `f(true) as size`.
|
||||
* Tests that the spill+tag-check path
|
||||
* doesn't lose the callee's selected tag.
|
||||
*
|
||||
* Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s ==
|
||||
* ww.s byte-identical (rule-10 stage symmetry).
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red on rows 1-4 or 7 means the
|
||||
* spill path is broken (non-IDENT scrutinee returned to the BP/RIP
|
||||
* read). Rows 5/6 red means narrow-scalar round-trip miscompiled at
|
||||
* the payload load (would surface if a future SIB regresses MOVQ to a
|
||||
* narrow load that drops the callee's extension).
|
||||
*/
|
||||
#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
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expected_exit;
|
||||
int stage_mask;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "call_as_size",
|
||||
"package main;\n"
|
||||
"fn f(b: bool) (size | str) = {\n"
|
||||
" if (b) { return 42: size; };\n"
|
||||
" return \"x\";\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let n = f(true) as size;\n"
|
||||
" return n: i32;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "call_as_str",
|
||||
"package main;\n"
|
||||
"fn f(b: bool) (size | str) = {\n"
|
||||
" if (b) { return \"AB\"; };\n"
|
||||
" return 0: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s = f(true) as str;\n"
|
||||
" return s.len: i32 + 40;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "call_as_namedvoid",
|
||||
"package main;\n"
|
||||
"type nv = void;\n"
|
||||
"fn f(b: bool) (size | nv) = {\n"
|
||||
" if (b) { return void: nv; };\n"
|
||||
" return 0: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let _ = f(true) as nv;\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "call_as_fnptr",
|
||||
/* #193: explicit deref required to call a fn-ptr (`(*p)(args)`).
|
||||
* The probe is wwstage cgtypeassert non-IDENT spill; deref-call
|
||||
* shape is orthogonal. */
|
||||
"package main;\n"
|
||||
"fn g(x: i32) i32 = { return x + 1; };\n"
|
||||
"fn f(b: bool) (*fn(x: i32) i32 | size) = {\n"
|
||||
" if (b) { return &g; };\n"
|
||||
" return 0: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p = f(true) as *fn(x: i32) i32;\n"
|
||||
" return (*p)(41);\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "call_as_u8",
|
||||
"package main;\n"
|
||||
"fn f(b: bool) (u8 | str) = {\n"
|
||||
" if (b) { return 42: u8; };\n"
|
||||
" return \"x\";\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let n = f(true) as u8;\n"
|
||||
" return n: i32;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "call_as_i16",
|
||||
"package main;\n"
|
||||
"fn f(b: bool) (i16 | str) = {\n"
|
||||
" if (b) { return -1: i16; };\n"
|
||||
" return \"x\";\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let n = f(true) as i16;\n"
|
||||
" if (n: i32 == -1) { return 42; };\n"
|
||||
" return 1;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "branched_call_as",
|
||||
"package main;\n"
|
||||
"fn f(b: bool) (size | str) = {\n"
|
||||
" if (b) { return 42: size; };\n"
|
||||
" return \"x\";\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let pick: bool = true;\n"
|
||||
" let n = f(pick) as size;\n"
|
||||
" return n: i32;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
};
|
||||
|
||||
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.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);
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
||||
rmdir(tmpdir);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *src)
|
||||
{
|
||||
char cmd[1024];
|
||||
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/tan_%d_d_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main772");
|
||||
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 = -1;
|
||||
if (build_via_driver(driver, tmpdir, src) == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
rc = runwait(outbin);
|
||||
}
|
||||
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/tan_%d_c_%d", getpid(), seq);
|
||||
snprintf(tdw, sizeof tdw, "/tmp/tan_%d_w_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main772");
|
||||
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 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,
|
||||
"typeassert_nonident[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,
|
||||
"typeassert_nonident[wwstage run][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].expected_exit);
|
||||
fail++;
|
||||
}
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"typeassert_nonident[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "typeassert_nonident: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "typeassert_nonident: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("typeassert_nonident: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user