wcc: route deref-call N_CALL to fn-VALUE fallback (#181)
Pre-fix the wwstage checker bailed asserttyped on the N_CALL whose
callee was N_UN TK_STAR over a *fn — selfhost/cmd/wcc/check.ww
exprtype's N_CALL arm only resolved IDENT/DOT-named callees and
early-returned nil for any other shape, leaving e.type_ unstamped
so the post-checker invariant fired. cstage worked because cexpr
recurses on the callee — TK_STAR's unop arm returns t->sub which
IS the TY_FN, no name path needed.
Fix: replace the `if (nm.len == 0) return nil` early-bail with
`if (nm.len > 0) { name-lookup }`, so non-named callees fall
through to the existing fn-VALUE fallback below (peel TPTR /
dealias to TFN / stamp the result type). Mirrors harec
check_autodereference at ref/harec/src/check.c:1566. cgen post
-#180+#185 already lowers the deref-call correctly, so lifting
the asserttyped bail is silent-SIGSEGV-safe per drew + ken.
Combined.ww regenerated for selfhost/cmd/{w6c,wwdump}/main
.combined.ww per #110 freshness gate.
Probe: test/wcc/766_star_fn_deref_call.c, 5 rows table-driven —
minimal / branched-callee / alias-chain / fn-with-args / fn
-tuple-return. Gate flip from 765: every row now gates BOTH
stages — cstage runtime, wwstage runtime, AND cs.s == ww.s byte
-id. This is the runtime coverage 765 deferred plus the symmetry
gate that proves both stages emit identical asm for the deref
-call shape. Closes the full c-cluster (#180 + #185 + #181 all 3
commits working together end-to-end).
This commit is contained in:
7
Makefile
7
Makefile
@@ -316,6 +316,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_typeeq_fn_ast \
|
||||
$(BIN)/test_amp_fn_ident \
|
||||
$(BIN)/test_star_fn_deref \
|
||||
$(BIN)/test_star_fn_deref_call \
|
||||
$(BIN)/test_use_promote_alias \
|
||||
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
@@ -618,6 +619,12 @@ $(BIN)/test_star_fn_deref: test/wcc/765_star_fn_deref.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_star_fn_deref_call: test/wcc/766_star_fn_deref_call.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 \
|
||||
|
||||
@@ -12408,7 +12408,6 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { nm = callee.str; };
|
||||
if (nm.len == 0) { return nil; };
|
||||
// #56: bare-leaf N_IDENT calls go through scopelookupprefer so
|
||||
// `foo()` inside module M binds to M.foo rather than another
|
||||
// module's same-leaf foo at the head of the flat scope bucket.
|
||||
@@ -12438,31 +12437,41 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// that coexists in the same scope (coexistence-equivalent of
|
||||
// cstage's use_alias; see lib/ww/sym.ww + memory
|
||||
// module_type_name_collision).
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
let ms: *sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != skind.SK_USE) {
|
||||
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
//
|
||||
// #181: a non-named callee (N_UN TK_STAR deref of a *fn local,
|
||||
// `(*f)(...)`; or any other expression-as-callee shape) has no
|
||||
// leaf to resolve here — skip the SK_FN name-lookup and let the
|
||||
// fn-VALUE fallback below peel TPTR / dealias to TFN. Mirrors
|
||||
// harec check_autodereference at ref/harec/src/check.c:1566.
|
||||
// cgen post-#180+#185 already lowers the deref-call correctly,
|
||||
// so lifting the asserttyped bail is silent-SIGSEGV-safe.
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
let ms: *sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != skind.SK_USE) {
|
||||
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
};
|
||||
};
|
||||
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
};
|
||||
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
// fn-decl's lhs is the return-type AST node. Mirrors cstage
|
||||
// cmd/wcc/check.c:984+ regular-CALL `n->type =
|
||||
// build_fn_type(c, s->decl)->ret` shape.
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
return s.decl.lhs;
|
||||
}; }; };
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
// fn-decl's lhs is the return-type AST node. Mirrors cstage
|
||||
// cmd/wcc/check.c:984+ regular-CALL `n->type =
|
||||
// build_fn_type(c, s->decl)->ret` shape.
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
return s.decl.lhs;
|
||||
}; }; };
|
||||
// A callee that is a fn-VALUE — a fn-pointer struct field
|
||||
// (`w.emit(...)`), local, or param — has no free SK_FN entry, so
|
||||
// the name lookup above misses. Read the result off the checked
|
||||
|
||||
@@ -2378,7 +2378,6 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { nm = callee.str; };
|
||||
if (nm.len == 0) { return nil; };
|
||||
// #56: bare-leaf N_IDENT calls go through scopelookupprefer so
|
||||
// `foo()` inside module M binds to M.foo rather than another
|
||||
// module's same-leaf foo at the head of the flat scope bucket.
|
||||
@@ -2408,31 +2407,41 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// that coexists in the same scope (coexistence-equivalent of
|
||||
// cstage's use_alias; see lib/ww/sym.ww + memory
|
||||
// module_type_name_collision).
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
let ms: *sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != skind.SK_USE) {
|
||||
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
//
|
||||
// #181: a non-named callee (N_UN TK_STAR deref of a *fn local,
|
||||
// `(*f)(...)`; or any other expression-as-callee shape) has no
|
||||
// leaf to resolve here — skip the SK_FN name-lookup and let the
|
||||
// fn-VALUE fallback below peel TPTR / dealias to TFN. Mirrors
|
||||
// harec check_autodereference at ref/harec/src/check.c:1566.
|
||||
// cgen post-#180+#185 already lowers the deref-call correctly,
|
||||
// so lifting the asserttyped bail is silent-SIGSEGV-safe.
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
let ms: *sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != skind.SK_USE) {
|
||||
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
};
|
||||
};
|
||||
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
};
|
||||
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
// fn-decl's lhs is the return-type AST node. Mirrors cstage
|
||||
// cmd/wcc/check.c:984+ regular-CALL `n->type =
|
||||
// build_fn_type(c, s->decl)->ret` shape.
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
return s.decl.lhs;
|
||||
}; }; };
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
// fn-decl's lhs is the return-type AST node. Mirrors cstage
|
||||
// cmd/wcc/check.c:984+ regular-CALL `n->type =
|
||||
// build_fn_type(c, s->decl)->ret` shape.
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
return s.decl.lhs;
|
||||
}; }; };
|
||||
// A callee that is a fn-VALUE — a fn-pointer struct field
|
||||
// (`w.emit(...)`), local, or param — has no free SK_FN entry, so
|
||||
// the name lookup above misses. Read the result off the checked
|
||||
|
||||
@@ -12408,7 +12408,6 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { nm = callee.str; };
|
||||
if (nm.len == 0) { return nil; };
|
||||
// #56: bare-leaf N_IDENT calls go through scopelookupprefer so
|
||||
// `foo()` inside module M binds to M.foo rather than another
|
||||
// module's same-leaf foo at the head of the flat scope bucket.
|
||||
@@ -12438,31 +12437,41 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// that coexists in the same scope (coexistence-equivalent of
|
||||
// cstage's use_alias; see lib/ww/sym.ww + memory
|
||||
// module_type_name_collision).
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
let ms: *sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != skind.SK_USE) {
|
||||
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
//
|
||||
// #181: a non-named callee (N_UN TK_STAR deref of a *fn local,
|
||||
// `(*f)(...)`; or any other expression-as-callee shape) has no
|
||||
// leaf to resolve here — skip the SK_FN name-lookup and let the
|
||||
// fn-VALUE fallback below peel TPTR / dealias to TFN. Mirrors
|
||||
// harec check_autodereference at ref/harec/src/check.c:1566.
|
||||
// cgen post-#180+#185 already lowers the deref-call correctly,
|
||||
// so lifting the asserttyped bail is silent-SIGSEGV-safe.
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
let ms: *sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != skind.SK_USE) {
|
||||
let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
};
|
||||
};
|
||||
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
};
|
||||
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
// fn-decl's lhs is the return-type AST node. Mirrors cstage
|
||||
// cmd/wcc/check.c:984+ regular-CALL `n->type =
|
||||
// build_fn_type(c, s->decl)->ret` shape.
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
return s.decl.lhs;
|
||||
}; }; };
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
// fn-decl's lhs is the return-type AST node. Mirrors cstage
|
||||
// cmd/wcc/check.c:984+ regular-CALL `n->type =
|
||||
// build_fn_type(c, s->decl)->ret` shape.
|
||||
e.type_ = tinfofornode(c, s.decl.lhs): *void;
|
||||
return s.decl.lhs;
|
||||
}; }; };
|
||||
// A callee that is a fn-VALUE — a fn-pointer struct field
|
||||
// (`w.emit(...)`), local, or param — has no free SK_FN entry, so
|
||||
// the name lookup above misses. Read the result off the checked
|
||||
|
||||
289
test/wcc/766_star_fn_deref_call.c
Normal file
289
test/wcc/766_star_fn_deref_call.c
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 766_star_fn_deref_call — root-cause lock for project #181, and the
|
||||
* live verification of the full deref-call c-cluster (#180 + #185 +
|
||||
* #181 working together end-to-end).
|
||||
*
|
||||
* Pre-#181 wwstage's checker bailed asserttyped on the N_CALL whose
|
||||
* callee was N_UN TK_STAR over a *fn — selfhost/cmd/wcc/check.ww
|
||||
* exprtype's N_CALL arm only resolved IDENT/DOT-named callees and
|
||||
* early-returned nil for any other shape, leaving e.type_ unstamped
|
||||
* so the post-checker invariant gate fired. With cgen post-#180+#185
|
||||
* already correct for the deref-call lowering, the safe fix was to
|
||||
* lift the asserttyped bail and route the callee through the
|
||||
* fn-VALUE fallback (autodereference + dealias to TY_FN, mirroring
|
||||
* harec check_autodereference at ref/harec/src/check.c:1566).
|
||||
*
|
||||
* Fix: selfhost/cmd/wcc/check.ww exprtype N_CALL arm — replace the
|
||||
* `if (nm.len == 0) return nil` early-bail with `if (nm.len > 0)
|
||||
* { name-lookup }`, so non-named callees fall through to the
|
||||
* existing fn-VALUE branch (peel TPTR, dealias to TFN, stamp the
|
||||
* result type). cstage check.c already worked because cexpr
|
||||
* recurses on the callee — TK_STAR's unop arm returns t->sub which
|
||||
* is TY_FN directly, no name path needed.
|
||||
*
|
||||
* Gate flip from 765: 765 was cstage-only because wwstage bailed
|
||||
* before reaching cgen. Post-#181 every row gates BOTH stages —
|
||||
* cstage runtime, wwstage runtime, AND cs.s == ww.s byte-id. This
|
||||
* is the runtime coverage 765 deferred plus the symmetry gate that
|
||||
* proves both stages emit identical asm for the deref-call shape.
|
||||
*
|
||||
* Coverage (same 5 rows as 765, now with STAGE_CS|STAGE_WW):
|
||||
* 1. minimal — `let f = &add1; (*f)(7) == 8`
|
||||
* 2. branched_callee — pick aa or bb by runtime cond
|
||||
* 3. alias_chain — `let f = &fn; let g = f; (*g)(7)`
|
||||
* 4. fn_with_args — multiple args, scalar + ptr mix
|
||||
* 5. fn_tuple_return — (i32, i32) return shape; multi-reg
|
||||
* return ABI survives the deref-call
|
||||
*
|
||||
* Gates per row:
|
||||
* a. cstage builds + runs, exit == expected (already worked
|
||||
* post-#180+#185; kept for c-cluster regression coverage).
|
||||
* b. wwstage builds + runs, exit == expected — the #181 lift
|
||||
* gate; pre-fix this row's asserttyped bailed before cgen.
|
||||
* c. cstage .s == wwstage .s byte-identical — rule-10 symmetry,
|
||||
* proves the checker change doesn't perturb the codegen.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means either the
|
||||
* checker N_CALL fn-VALUE route regressed, the cgen deref-call
|
||||
* lowering regressed (#180/#185), or stage symmetry drifted.
|
||||
*/
|
||||
#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[] = {
|
||||
{ "minimal",
|
||||
"fn add1(x: i32) i32 = { return x + 1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f: *fn(x: i32) i32 = &add1;\n"
|
||||
" return (*f)(7);\n"
|
||||
"};\n",
|
||||
8,
|
||||
STAGE_CS | STAGE_WW },
|
||||
/* Branched-callee: the if-arm forces a runtime choice between
|
||||
* two distinct fn addresses, defeating any constant-aliasing
|
||||
* mask of the deref miscompile (#105 lesson). */
|
||||
{ "branched_callee",
|
||||
"fn aa(x: i32) i32 = { return x; };\n"
|
||||
"fn bb(x: i32) i32 = { return x + 100; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let pick: i32 = 1;\n"
|
||||
" let f: *fn(x: i32) i32 = &aa;\n"
|
||||
" if (pick != 0) { f = &bb; };\n"
|
||||
" return (*f)(7);\n"
|
||||
"};\n",
|
||||
107,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "alias_chain",
|
||||
"fn add1(x: i32) i32 = { return x + 1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f: *fn(x: i32) i32 = &add1;\n"
|
||||
" let g: *fn(x: i32) i32 = f;\n"
|
||||
" return (*g)(7);\n"
|
||||
"};\n",
|
||||
8,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "fn_with_args",
|
||||
"fn many(a: i32, b: i32, p: *i32) i32 = { return a + b + *p; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let z: i32 = 5;\n"
|
||||
" let f: *fn(a: i32, b: i32, p: *i32) i32 = &many;\n"
|
||||
" return (*f)(3, 7, &z);\n"
|
||||
"};\n",
|
||||
15,
|
||||
STAGE_CS | STAGE_WW },
|
||||
/* Tuple-return: exercises the multi-register return ABI through
|
||||
* the deref-call. (i32, i32) keeps it simple while still covering
|
||||
* the multi-reg path. */
|
||||
{ "fn_tuple_return",
|
||||
"fn pair(x: i32) (i32, i32) = { return (x, x + 1); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f: *fn(x: i32) (i32, i32) = &pair;\n"
|
||||
" let a, b = (*f)(7);\n"
|
||||
" return a + b;\n"
|
||||
"};\n",
|
||||
15,
|
||||
STAGE_CS | STAGE_WW },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *src_path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(src_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);
|
||||
}
|
||||
|
||||
/* run_row — build + run; returns the binary's exit code (-1 on
|
||||
* build failure). */
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[256], base[64], outbin[512];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcsfdc_%d_d_%d", getpid(), seq);
|
||||
snprintf(src, sizeof src, "%s/main766.ww", tmpdir);
|
||||
snprintf(base, sizeof base, "main766");
|
||||
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 (filed bug per 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[256], tdc[256], tdw[256], base[64], cs[512], ws[512];
|
||||
snprintf(tdc, sizeof tdc, "/tmp/wcsfdc_%d_c_%d", getpid(), seq);
|
||||
snprintf(tdw, sizeof tdw, "/tmp/wcsfdc_%d_w_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main766");
|
||||
mkdir(tdc, 0755);
|
||||
mkdir(tdw, 0755);
|
||||
snprintf(src, sizeof src, "%s/main766.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.s", tdc, base);
|
||||
|
||||
snprintf(src, sizeof src, "%s/main766.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.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[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
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,
|
||||
"star_fn_deref_call[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,
|
||||
"star_fn_deref_call[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,
|
||||
"star_fn_deref_call[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "star_fn_deref_call: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "star_fn_deref_call: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("star_fn_deref_call: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user