test: port the cstage-only rejects to ww; retire 760_def_widen_const + 783_amp_fn_assign_run
Both carriers were already slim: the r76_def_widen_* byte-id legs are owned by the test-data-byteid blanket, 783's positive byte rows by the r78_amp_fn_assign_* fixtures plus that same blanket, and its symmetric non-tagged rejects by the corpus //ww:error fixtures. The unowned remainder is three cstage-only loud-rejects (the 760 narrowing def-ref and 783's two tagged-slot rows); the wwstage over-accepts (no def-init assignability check; #214 void-variant leniency) are documented at the site per the residual audit, never asserted as desired — the rows graduate to //ww:error fixtures when the wwstage side tightens. Bundled: one file, one concern (cstage-reject pins awaiting wwstage graduation).
This commit is contained in:
3
Makefile
3
Makefile
@@ -407,7 +407,8 @@ OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%)
|
|||||||
# run-stderr probes, .wwi round-trip trees, tool-dump AST proofs,
|
# run-stderr probes, .wwi round-trip trees, tool-dump AST proofs,
|
||||||
# cstage-only rejects). Compiler/driver gates like test/sep: they run
|
# cstage-only rejects). Compiler/driver gates like test/sep: they run
|
||||||
# under test-compiler.
|
# under test-compiler.
|
||||||
MISC_WW_TESTS = test/misc/arrglob_test.ww test/misc/packedwwi_test.ww
|
MISC_WW_TESTS = test/misc/arrglob_test.ww test/misc/packedwwi_test.ww \
|
||||||
|
test/misc/reject_test.ww
|
||||||
MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%)
|
MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%)
|
||||||
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||||
test/wcc/991_w6a_ww.c \
|
test/wcc/991_w6a_ww.c \
|
||||||
|
|||||||
107
test/misc/reject_test.ww
Normal file
107
test/misc/reject_test.ww
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package reject_test;
|
||||||
|
|
||||||
|
// Cstage-only loud-reject pins, ported from the retired native
|
||||||
|
// carriers test/wcc/760_def_widen_const.c and
|
||||||
|
// test/wcc/783_amp_fn_assign_run.c. Each row asserts ONLY the cstage
|
||||||
|
// reject; the wwstage side over-accepts today and that over-accept is
|
||||||
|
// documented here (residual audit, test-infra-v2-residual-20260808),
|
||||||
|
// never asserted as desired — these rows graduate to //ww:error
|
||||||
|
// corpus fixtures the moment the wwstage checker gains the matching
|
||||||
|
// reject.
|
||||||
|
//
|
||||||
|
// 760 (#113 def_cast_fits): an out-of-range narrowing def-ref must
|
||||||
|
// fail the cstage build loud — the const-scoped widen must never
|
||||||
|
// become a silent truncation. wwstage has no def-init assignability
|
||||||
|
// check at all (pre-existing leanness) and accepts. The r76_def_widen_*
|
||||||
|
// byte-id legs the carrier also drove are owned by the
|
||||||
|
// test-data-byteid blanket over those fixtures.
|
||||||
|
//
|
||||||
|
// 783 (#206 gate, tagged rows): the two NEGATIVE tagged-slot rows are
|
||||||
|
// cstage-only pending #214 — wwstage's `(X | void)` tagged
|
||||||
|
// assignability leniently matches any *fn against the void variant
|
||||||
|
// (the accept-INVALID hole; its close is required before wwstage can
|
||||||
|
// be the authoritative checker). The positive byte-id rows are owned
|
||||||
|
// by the r78_amp_fn_assign_* fixtures plus the test-data-byteid
|
||||||
|
// blanket; the symmetric non-tagged rejects by the corpus
|
||||||
|
// r783_amp_fn_assign_neg_* //ww:error fixtures.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import testenv;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
fn fail(label: str, why: str) void = {
|
||||||
|
let m: str = strings.concat("reject FAIL: ", label, " -- ", why,
|
||||||
|
"\n");
|
||||||
|
os.write(2, m.ptr, m.len: u64);
|
||||||
|
assert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tmo() time.duration = {
|
||||||
|
return (180i64 * (time.second: i64)): time.duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn defwidennarrow() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
testenv.writefile(strings.concat(td, "/nw.ww"), strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"def BIG_I32: i32 = 70000;\n",
|
||||||
|
"def S_I16: i16 = BIG_I32;\n",
|
||||||
|
"export fn main() i32 = { let s: i16 = S_I16; ",
|
||||||
|
"return s: i32; };\n"));
|
||||||
|
let av: []str = [testenv.driver("w6c"), "-o",
|
||||||
|
strings.concat(td, "/nw.s"), strings.concat(td, "/nw.ww")];
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, "narrow", av, tmo(), &co);
|
||||||
|
// A deadline kill is not a reject; only a loud EXIT failure is.
|
||||||
|
if (co.termination != exec.termination.EXIT) {
|
||||||
|
fail("narrow", "w6c did not terminate normally");
|
||||||
|
};
|
||||||
|
if (co.code == 0) {
|
||||||
|
fail("narrow", strings.concat("w6c exited 0 (expected loud ",
|
||||||
|
"failure, no silent truncation)"));
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cstagerejectrow(label: str, src: str) void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
testenv.writefile(strings.concat(td, "/main783.ww"), src);
|
||||||
|
let av: []str = [testenv.driver("ww"), "build", "-o", "main783",
|
||||||
|
"main783.ww"];
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, label, av, tmo(), &co);
|
||||||
|
if (co.termination != exec.termination.EXIT) {
|
||||||
|
fail(label, "cstage build did not terminate normally");
|
||||||
|
};
|
||||||
|
if (co.code == 0) {
|
||||||
|
fail(label, "cstage built but expected reject");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn ampfnassigntagged() void = {
|
||||||
|
// Two same-signature ptr-to-fn variants: a direct &fn is ambiguous
|
||||||
|
// and must never silently bind to one.
|
||||||
|
cstagerejectrow("neg_ambiguous_tagged", strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"type reader = fn(x: i32) i32;\n",
|
||||||
|
"type writer = fn(x: i32) i32;\n",
|
||||||
|
"fn myfn(x: i32) i32 = { return x; };\n",
|
||||||
|
"fn main() i32 = {\n",
|
||||||
|
" let x: (*reader | *writer | void) = &myfn;\n",
|
||||||
|
" return 0;\n",
|
||||||
|
"};\n"));
|
||||||
|
// Laundering a materialized *fn into a (*reader|void) slot:
|
||||||
|
// cstage rejects nominally (harec types.c:1039-1066 mirror).
|
||||||
|
cstagerejectrow("neg_launder_tagged", strings.concat(
|
||||||
|
"package main;\n",
|
||||||
|
"type reader = fn(x: i32) i32;\n",
|
||||||
|
"fn rd(x: i32) i32 = { return x + 1; };\n",
|
||||||
|
"fn main() i32 = {\n",
|
||||||
|
" let p = &rd;\n",
|
||||||
|
" let s: (*reader | void) = p;\n",
|
||||||
|
" return 0;\n",
|
||||||
|
"};\n"));
|
||||||
|
};
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
* 760_def_widen_const — const-scoped cross-prim-width integer widening
|
|
||||||
* in a `def` initializer (PROJECT #113).
|
|
||||||
*
|
|
||||||
* The gap: a def-reference types as the referent's DECLARED type, so
|
|
||||||
* `def INT_MIN: int = I32_MIN;` saw the rhs as i32 and the def-init
|
|
||||||
* assignability check (cmd/wcc/check.c, type_assignable) rejected i32 →
|
|
||||||
* int. The fix: in a def initializer the rhs is a flexible constant, so
|
|
||||||
* a const integer that folds to a concrete primitive width widens to
|
|
||||||
* the declared integer type exactly as an UNTYPED_INT literal would,
|
|
||||||
* when the value fits the target (def_cast_fits). This is the
|
|
||||||
* const-scoped subset of Hare's general integer-widening; non-const
|
|
||||||
* widening stays stricter in ww (PROJECT #115). It rides the #88
|
|
||||||
* eval_def_const fold + stamp, so cgen's literal-only emit lays the
|
|
||||||
* 8-byte DATA row with no codegen change.
|
|
||||||
*
|
|
||||||
* The fix is cstage-only: the wwstage checker (selfhost/cmd/wcc/check.ww,
|
|
||||||
* "let init / return assignability") intentionally never checks def-init
|
|
||||||
* assignability (it stays quiet, leaving full inference to the C side),
|
|
||||||
* so it never rejected the widening — the #88 stamp already laid the
|
|
||||||
* correct row. Relaxing the cstage aligns the richer side DOWN to the
|
|
||||||
* leaner side (CLAUDE.md rule 10), and both stages stamp the identical
|
|
||||||
* folded value, so the emitted asm is byte-identical.
|
|
||||||
*
|
|
||||||
* Coverage:
|
|
||||||
* 1. The r76_def_widen_* fixtures own symmetric build/runtime semantics.
|
|
||||||
* 2. BYTE-ID: cstage w6c vs wwstage w6c_ww `.s` for every fixture row
|
|
||||||
* (proves the const-fold stamp is bit-identical across stages).
|
|
||||||
* 3. FAIL-LOUD (rule 7), cstage only: an out-of-range narrowing
|
|
||||||
* def-ref (`def S: i16 = BIG;` where BIG overflows i16) must still
|
|
||||||
* fail the cstage build — def_cast_fits keeps the widening from
|
|
||||||
* becoming a silent truncation. Asserted on the cstage alone
|
|
||||||
* because the wwstage has no def-init assignability check at all
|
|
||||||
* (pre-existing leanness, unchanged by #113).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include "wwtestpkg.h"
|
|
||||||
|
|
||||||
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 *path; };
|
|
||||||
|
|
||||||
static const struct row exec_rows[] = {
|
|
||||||
{ "i32-to-int-neg",
|
|
||||||
"test/wcc/data/r76_def_widen_i32_int_neg/case.ww" },
|
|
||||||
{ "i32-to-int-small",
|
|
||||||
"test/wcc/data/r76_def_widen_i32_int_small/case.ww" },
|
|
||||||
{ "u64-to-size",
|
|
||||||
"test/wcc/data/r76_def_widen_u64_size/case.ww" },
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- shared .s emit -------------------------------------------------- */
|
|
||||||
|
|
||||||
static int
|
|
||||||
emit_s(const char *w6c, const char *src, char *out_s, size_t cap)
|
|
||||||
{
|
|
||||||
char cmd[1024];
|
|
||||||
snprintf(cmd, sizeof cmd, "timeout 180 %s -o %s %s 2>/dev/null",
|
|
||||||
w6c, out_s, src);
|
|
||||||
(void)cap;
|
|
||||||
return runwait(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 2. byte-identity of cstage vs wwstage .s ----------------------- */
|
|
||||||
|
|
||||||
static int
|
|
||||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
|
||||||
{
|
|
||||||
char cs[64], ws[64];
|
|
||||||
snprintf(cs, sizeof cs, "/tmp/dwc_bi_%d_%d_c.s", getpid(), i);
|
|
||||||
snprintf(ws, sizeof ws, "/tmp/dwc_bi_%d_%d_w.s", getpid(), i);
|
|
||||||
|
|
||||||
char w6c[640], w6c_ww[640];
|
|
||||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
|
||||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
|
||||||
|
|
||||||
if (emit_s(w6c, r->path, cs, sizeof cs) != 0) {
|
|
||||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (emit_s(w6c_ww, r->path, ws, sizeof ws) != 0) {
|
|
||||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
|
||||||
unlink(cs);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *fc = fopen(cs, "rb");
|
|
||||||
FILE *fw = fopen(ws, "rb");
|
|
||||||
int rc = 0;
|
|
||||||
if (!fc || !fw) {
|
|
||||||
rc = -1;
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
if (rc != 0)
|
|
||||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
|
||||||
r->label);
|
|
||||||
unlink(cs); unlink(ws);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 3. fail-loud (cstage only): out-of-range narrowing def-ref ----- */
|
|
||||||
|
|
||||||
/* `def BIG: i32 = 70000; def S: i16 = BIG;` — 70000 overflows i16, so
|
|
||||||
* def_cast_fits says the value does not fit and the const-scoped widen
|
|
||||||
* does NOT fire; the def-init assignability check then rejects i32 →
|
|
||||||
* i16. Compile via w6c only; success means the build FAILED as required
|
|
||||||
* (nonzero w6c exit), never a silent truncation. */
|
|
||||||
static const char narrow_src[] =
|
|
||||||
"def BIG_I32: i32 = 70000;\n"
|
|
||||||
"def S_I16: i16 = BIG_I32;\n"
|
|
||||||
"export fn main() i32 = { let s: i16 = S_I16; return s: i32; };\n";
|
|
||||||
|
|
||||||
static int
|
|
||||||
cstage_narrow_fails(const char *w6c)
|
|
||||||
{
|
|
||||||
char src[64], s[64], cmd[1024];
|
|
||||||
snprintf(src, sizeof src, "/tmp/dwc_nl_%d.ww", getpid());
|
|
||||||
snprintf(s, sizeof s, "/tmp/dwc_nl_%d.s", getpid());
|
|
||||||
|
|
||||||
FILE *f = fopen(src, "wb");
|
|
||||||
if (!f) return -1;
|
|
||||||
wwtest_fputs(narrow_src, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "timeout 180 %s -o %s %s 2>/dev/null",
|
|
||||||
w6c, s, src);
|
|
||||||
int rc = runwait(cmd);
|
|
||||||
unlink(src); unlink(s);
|
|
||||||
if (rc == 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"narrow[cstage]: w6c exited 0 (expected loud failure, "
|
|
||||||
"no silent truncation)\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (rc == 124) {
|
|
||||||
fprintf(stderr, "narrow[cstage]: w6c timed out\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 w6c[1024], w6c_ww[1024];
|
|
||||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
|
||||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
|
||||||
|
|
||||||
int have_ww = (access(w6c_ww, X_OK) == 0);
|
|
||||||
int total = 0, fail = 0;
|
|
||||||
|
|
||||||
int nexec = (int)(sizeof exec_rows / sizeof exec_rows[0]);
|
|
||||||
/* Runtime rows moved to r76_def_widen_*; retain byte-id and cstage reject. */
|
|
||||||
if (have_ww) {
|
|
||||||
for (int i = 0; i < nexec; i++) {
|
|
||||||
total++;
|
|
||||||
if (asm_byte_identical(bin, &exec_rows[i], i) != 0)
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. fail-loud (cstage only) */
|
|
||||||
total++;
|
|
||||||
if (cstage_narrow_fails(w6c) != 0) fail++;
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"def_widen_const: %d/%d fixtures failed\n", fail, total);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("def_widen_const: %d/%d ok\n", total, total);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,347 +0,0 @@
|
|||||||
/*
|
|
||||||
* 783_amp_fn_assign_run — project #206 close. Pins that a bare
|
|
||||||
* `&fn_name` is assignable into a `*<fn-alias>` slot and a
|
|
||||||
* `(*<fn-alias> | void)` tagged slot (the io vstream vtable shape)
|
|
||||||
* WITHOUT the explicit `(&fn): *alias` cast that the lib/{io,memio,
|
|
||||||
* fmt,bufio,log} vstream surfaces currently carry. #94 fold-eFinal
|
|
||||||
* drops those casts wholesale once this is green.
|
|
||||||
*
|
|
||||||
* THE FIX (Option C, both stages, checker-only — cgen is a no-op
|
|
||||||
* fn-pointer reinterpret): type_assignable / isassignable stay fully
|
|
||||||
* NOMINAL (a materialized `*fn` value laundered into a `*alias` is
|
|
||||||
* rejected, mirror of harec ref/harec/src/types.c:1039-1066). A
|
|
||||||
* caller-site gate `assignable_addrfn` (cmd/wcc/check.c) /
|
|
||||||
* `assignableaddrfn` (selfhost/cmd/wcc/check.ww) admits ONLY a DIRECT
|
|
||||||
* `&`-of-fn-ident whose signature structurally matches the
|
|
||||||
* destination's pointed-to fn alias, or — for a tagged dst — the
|
|
||||||
* single matching ptr-to-fn variant (>=2 same-sig variants is
|
|
||||||
* ambiguous → reject). This is harec's adopt-the-alias-at-the-`&`-site
|
|
||||||
* rule (ref/harec/src/check.c:3594-3626) without threading a type
|
|
||||||
* hint through the bottom-up expression checker.
|
|
||||||
*
|
|
||||||
* ROW POLARITY:
|
|
||||||
* POSITIVE execution assertions now live in the declarative compiler
|
|
||||||
* corpus. Their residual rows here compile BOTH stages and assert
|
|
||||||
* cs.s == ww.s (rule-10). `let_call` and `structlit_build` pin the
|
|
||||||
* let-binding and struct-literal-field-init gate sites.
|
|
||||||
* `fieldstore_dispatch` pins the eFinal SHIPPING shape: a bare &fn
|
|
||||||
* field-STORE into one slot of the three-slot io.vtable
|
|
||||||
* (reader/writer/closer), then a dispatcher match on a *vtable
|
|
||||||
* POINTER-param. It remains byte-id-clean: the
|
|
||||||
* single-slot local-composite zero-init divergence (project #213,
|
|
||||||
* reproduces with the cast form and a pure `(i32|void)` field — NOT a
|
|
||||||
* #206 regression) is a single-field artifact the real three-slot
|
|
||||||
* io.vtable does not hit.
|
|
||||||
*
|
|
||||||
* The symmetric non-tagged rejection rows (`neg_launder`,
|
|
||||||
* `neg_samesig`) live in the declarative compiler corpus. Both checker
|
|
||||||
* paths report the common diagnostic fragment "not assignable".
|
|
||||||
*
|
|
||||||
* NEGATIVE tagged-slot rows (`neg_ambiguous_tagged`,
|
|
||||||
* `neg_launder_tagged`) are pinned CSTAGE-ONLY. cstage REJECTS both.
|
|
||||||
* wwstage WRONGLY ACCEPTS them: its `(X | void)` tagged-assignability
|
|
||||||
* leniently matches any `*fn` against the `void` variant (project
|
|
||||||
* #214, the void-variant OVER-acceptance). #214 is an accept-INVALID
|
|
||||||
* hole and its close is REQUIRED BEFORE wwstage can become the
|
|
||||||
* authoritative selfhost checker; these rows graduate to STAGE_WW
|
|
||||||
* when #214 closes. Mirrors the 777/780/781/782 STAGE_CS carve-out.
|
|
||||||
*
|
|
||||||
* GATE POLARITY: must stay GREEN. Red means the #206 gate over- or
|
|
||||||
* under-accepts or the stage output diverged.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <errno.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; /* local source for asymmetric controls */
|
|
||||||
const char *fixture; /* declarative corpus source for byte rows */
|
|
||||||
int stage_mask;
|
|
||||||
int byte_id; /* assert cs.s == ww.s */
|
|
||||||
int expect_fail; /* 1 = build MUST fail */
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct row rows[] = {
|
|
||||||
/* POSITIVE byte-id: bare &fn into a *<fn-alias> let, then call. */
|
|
||||||
{ "let_call",
|
|
||||||
NULL,
|
|
||||||
"test/wcc/data/r78_amp_fn_assign_let_call/case.ww",
|
|
||||||
STAGE_CS | STAGE_WW, 1, 0 },
|
|
||||||
|
|
||||||
/* POSITIVE byte-id: struct-literal field-init of a (*alias|void)
|
|
||||||
* slot with bare &fn (the Hare `vtable{reader=&fn}` shape). Build
|
|
||||||
* only — no match (project #212 blocks struct-lit-tagged + match
|
|
||||||
* on wwstage cgen); the checker gate acceptance is what this pins. */
|
|
||||||
{ "structlit_build",
|
|
||||||
NULL,
|
|
||||||
"test/wcc/data/r78_amp_fn_assign_structlit/case.ww",
|
|
||||||
STAGE_CS | STAGE_WW, 1, 0 },
|
|
||||||
|
|
||||||
/* POSITIVE byte-id: bare &fn FIELD-STORE into a (*reader|void) slot
|
|
||||||
* of the THREE-slot vtable (the exact io.vtable reader/writer/closer
|
|
||||||
* eFinal shape, test 775), then dispatcher match on a *vtable
|
|
||||||
* POINTER-param. The live-code-address call-through AND the void-arm
|
|
||||||
* discrimination are exercised, and cs.s == ww.s (rule-10). This is
|
|
||||||
* the eFinal SHIPPING path, so it MUST be byte-id-clean: the single-
|
|
||||||
* slot local-composite zero-init divergence (#213) is a single-field
|
|
||||||
* artifact that the real three-slot io.vtable does not hit. */
|
|
||||||
{ "fieldstore_dispatch",
|
|
||||||
NULL,
|
|
||||||
"test/wcc/data/r78_amp_fn_assign_fieldstore_dispatch/case.ww",
|
|
||||||
STAGE_CS | STAGE_WW, 1, 0 },
|
|
||||||
|
|
||||||
/* NEGATIVE cstage-only (#214 wwstage void-variant over-acceptance):
|
|
||||||
* two same-signature ptr-to-fn variants in the tagged dst — a direct
|
|
||||||
* &fn is ambiguous and must be rejected, never silently bound to one. */
|
|
||||||
{ "neg_ambiguous_tagged",
|
|
||||||
"package main;\n"
|
|
||||||
"type reader = fn(x: i32) i32;\n"
|
|
||||||
"type writer = fn(x: i32) i32;\n"
|
|
||||||
"fn myfn(x: i32) i32 = { return x; };\n"
|
|
||||||
"fn main() i32 = {\n"
|
|
||||||
" let x: (*reader | *writer | void) = &myfn;\n"
|
|
||||||
" return 0;\n"
|
|
||||||
"};\n",
|
|
||||||
NULL,
|
|
||||||
STAGE_CS, 0, 1 },
|
|
||||||
|
|
||||||
/* NEGATIVE cstage-only (#214): laundering a materialized *fn into a
|
|
||||||
* (*reader|void) tagged slot. cstage rejects nominally; wwstage's
|
|
||||||
* void-variant leniency wrongly accepts. */
|
|
||||||
{ "neg_launder_tagged",
|
|
||||||
"package main;\n"
|
|
||||||
"type reader = fn(x: i32) i32;\n"
|
|
||||||
"fn rd(x: i32) i32 = { return x + 1; };\n"
|
|
||||||
"fn main() i32 = {\n"
|
|
||||||
" let p = &rd;\n"
|
|
||||||
" let s: (*reader | void) = p;\n"
|
|
||||||
" return 0;\n"
|
|
||||||
"};\n",
|
|
||||||
NULL,
|
|
||||||
STAGE_CS, 0, 1 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
cleanup_tmp(const char *tmpdir, const char *base)
|
|
||||||
{
|
|
||||||
char p[1024];
|
|
||||||
int rc = 0;
|
|
||||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base);
|
|
||||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
|
||||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base);
|
|
||||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
|
||||||
/* #93: the sep scratch dir + the tmpdir-owned outputs. */
|
|
||||||
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork", tmpdir, base);
|
|
||||||
if (runwait(p) != 0) rc = -1;
|
|
||||||
if (rmdir(tmpdir) != 0) rc = -1;
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
write_source(const char *path, const struct row *r)
|
|
||||||
{
|
|
||||||
FILE *in = NULL;
|
|
||||||
if (r->fixture != NULL) {
|
|
||||||
in = fopen(r->fixture, "rb");
|
|
||||||
if (in == NULL) return -1;
|
|
||||||
}
|
|
||||||
FILE *f = fopen(path, "wb");
|
|
||||||
if (!f) {
|
|
||||||
if (in != NULL) fclose(in);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (in != NULL) {
|
|
||||||
int ioerr = 0;
|
|
||||||
for (;;) {
|
|
||||||
int ch = fgetc(in);
|
|
||||||
if (ch == EOF) break;
|
|
||||||
if (fputc(ch, f) == EOF) { ioerr = 1; break; }
|
|
||||||
}
|
|
||||||
if (ferror(in)) ioerr = 1;
|
|
||||||
if (fclose(in) != 0) ioerr = 1;
|
|
||||||
if (fclose(f) != 0) ioerr = 1;
|
|
||||||
return ioerr ? -1 : 0;
|
|
||||||
} else {
|
|
||||||
if (fputs(r->src, f) == EOF) {
|
|
||||||
fclose(f);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fclose(f) == 0 ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
build_via_driver(const char *driver, const char *tmpdir, const char *src)
|
|
||||||
{
|
|
||||||
char cmd[2048];
|
|
||||||
/* #93 sep layout: `-o <src-stem>` relocates artifacts to
|
|
||||||
* <stem>.sepwork/ under tmpdir. expect_fail rows still fail: a checker
|
|
||||||
* error makes w6c return nonzero regardless of artifact placement. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && b='%s'; timeout 180 %s build "
|
|
||||||
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
|
|
||||||
tmpdir, src, driver);
|
|
||||||
return runwait(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build_ok — 1 iff the build succeeds (used by expect_fail rows). */
|
|
||||||
static int
|
|
||||||
build_ok(const char *driver, const struct row *r, int seq)
|
|
||||||
{
|
|
||||||
(void)seq;
|
|
||||||
char tmpdir[] = "/tmp/afa_nf_XXXXXX";
|
|
||||||
char src[512], base[64];
|
|
||||||
if (mkdtemp(tmpdir) == NULL) return -1;
|
|
||||||
snprintf(base, sizeof base, "main783");
|
|
||||||
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
|
||||||
if (write_source(src, r) != 0) {
|
|
||||||
if (cleanup_tmp(tmpdir, base) != 0)
|
|
||||||
fprintf(stderr, "amp_fn_assign[%s]: setup cleanup failed\n", r->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int br = build_via_driver(driver, tmpdir, src);
|
|
||||||
if (cleanup_tmp(tmpdir, base) != 0) {
|
|
||||||
fprintf(stderr, "amp_fn_assign[%s]: cleanup failed\n", r->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return br == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel owned trees
|
|
||||||
* keep each driver's retained <stem>.sepwork/__root.s distinct. */
|
|
||||||
static int
|
|
||||||
asm_byte_identical(const char *cdrv, const char *wdrv, const struct row *r,
|
|
||||||
int seq)
|
|
||||||
{
|
|
||||||
(void)seq;
|
|
||||||
char src[512], base[64], cs[512], ws[512];
|
|
||||||
char tdc[] = "/tmp/afa_c_XXXXXX";
|
|
||||||
char tdw[] = "/tmp/afa_w_XXXXXX";
|
|
||||||
snprintf(base, sizeof base, "main783");
|
|
||||||
if (mkdtemp(tdc) == NULL) return -1;
|
|
||||||
if (mkdtemp(tdw) == NULL) {
|
|
||||||
if (cleanup_tmp(tdc, base) != 0)
|
|
||||||
fprintf(stderr, "amp_fn_assign[%s]: setup cleanup failed\n", r->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
|
|
||||||
int rc = -1;
|
|
||||||
if (write_source(src, r) != 0) goto out;
|
|
||||||
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/%s.ww", tdw, base);
|
|
||||||
if (write_source(src, r) != 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:
|
|
||||||
if (cleanup_tmp(tdc, base) != 0) {
|
|
||||||
fprintf(stderr, "amp_fn_assign[%s]: c cleanup failed\n", r->label);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
if (cleanup_tmp(tdw, base) != 0) {
|
|
||||||
fprintf(stderr, "amp_fn_assign[%s]: ww cleanup failed\n", r->label);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
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) && r->expect_fail) {
|
|
||||||
total++;
|
|
||||||
int got = build_ok(cdrv, r, seq++);
|
|
||||||
if (got != 0) {
|
|
||||||
if (got > 0)
|
|
||||||
fprintf(stderr, "amp_fn_assign[cs][%s]: built but expected reject\n",
|
|
||||||
r->label);
|
|
||||||
else
|
|
||||||
fprintf(stderr, "amp_fn_assign[cs][%s]: setup or cleanup failed\n",
|
|
||||||
r->label);
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wwpresent && (r->stage_mask & STAGE_WW)) {
|
|
||||||
if (r->expect_fail) {
|
|
||||||
total++;
|
|
||||||
int got = build_ok(wdrv, r, seq++);
|
|
||||||
if (got != 0) {
|
|
||||||
if (got > 0)
|
|
||||||
fprintf(stderr, "amp_fn_assign[ww][%s]: built but expected reject\n",
|
|
||||||
r->label);
|
|
||||||
else
|
|
||||||
fprintf(stderr, "amp_fn_assign[ww][%s]: setup or cleanup failed\n",
|
|
||||||
r->label);
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
} else if (r->byte_id) {
|
|
||||||
total++;
|
|
||||||
if (asm_byte_identical(cdrv, wdrv, r, seq++) != 0) {
|
|
||||||
fprintf(stderr, "amp_fn_assign[byte-id][%s]: cstage vs wwstage asm differs\n",
|
|
||||||
r->label);
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "amp_fn_assign: %d/%d checks failed\n", fail, total);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("amp_fn_assign: %d/%d ok\n", total, total);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user