test: migrate Fam13 misc checker/coercion value tests to @test (#5-C6)

Final fold-2 chunk. The 12 Fam13 single-file value drivers move from
test/wcc/*_run.c into in-language @test row-tables under test/lang/:

- value rows -> test/lang/*_test.ww (12 files)
- reject rows -> runww //ww:error carriers (13, dual-stage non-vacuous)
- nullable abort rows -> runww //ww:run-exit 1 carriers (3)
- 953_globalslice_arg -> _runonly (cs!=ww checker divergence, #28)
- 788 value_not_type_neg + 953_arrlit_slice reject_assign kept as slim
  rc-only .c pins (divergent-diag dual-reject, mutation-gated); 788 #29

Coverage parity verified row-by-row vs each retired driver; advisor-
ratified carve taxonomy; two-round reviewed. Floor ratchet follows.
This commit is contained in:
2026-06-24 22:59:32 +09:00
parent 246e5bb90e
commit 132ea4ee60
41 changed files with 950 additions and 3160 deletions

View File

@@ -1,256 +0,0 @@
/*
* 782_fieldfn_leaf_collide_run — project #211 close, the cgen sibling of
* #208 (the checker fix).
*
* SHAPE: a value-receiver fn-pointer FIELD call `s.pull(...)` whose leaf
* name `pull` COLLIDES with a same-module GLOBAL fn `pull` of a DIFFERENT
* register shape — the field returns a tagged `(i64 | sentinel)` (2-word
* AX=tag/DX=word0 ABI), the global returns a scalar `i64` (1-word AX).
* cstage resolves the call result from the CALLEE's own type (the field's
* fn type, cmd/wcc/check.c:1433/1490 `n->type = u->ret`), so it reads the
* tagged 2-word return correctly. PRE-#211 wwstage cgen re-derived the
* source shape by NAME (rhstaggedabicall → fnretlookupmod over the leaf,
* with the receiver VARIABLE name as the "module"), mis-bound the scalar
* global, and widened a 1-word AX into the tagged slot — a silent cs≠ww
* miscompile (wrong runtime + divergent .s).
*
* THE FIX (#211, align wwstage UP to cstage, structural): rhstaggedabicall
* (selfhost/cmd/wcc/cgenutil.ww) reads the checker-stamped result type off
* the N_CALL node (`src.type_`, which check.ww's N_CALL stamps to the
* callee fn-type's ret in both the SK_FN and the fn-VALUE/field paths)
* instead of the leaf-name lookup. Mirrors harec selecting by interned
* type id, not name (ref/harec/src/types.c:714). #211 was MASKED until
* #208 landed: pre-#208 the wwstage checker rejected this shape ("is/as:
* operand is not a tagged union") before cgen ran, so the cgen path was
* unreachable.
*
* row | what it pins
* ---------------------+----------------------------------------------
* field_vs_global_leaf | s.pull(&s,5) routes to the FIELD impl
* | (srcpull → 105), NOT the same-named global
* | `pull` (→ 14). `r is i64` / `r as i64` prove
* | the result is the tagged field type. The
* | global stays live (g = pull(3) = 14) so the
* | collision is real, not dead-code-elided.
*
* Graduated to STAGE_CS | STAGE_WW + byte_id on #211 close: builds + runs
* on both stages (exit 42) and asserts cs.s == ww.s (rule-10). A red means
* #211 regressed — wwstage re-derived the call-result shape by leaf name
* again, or the cstage call-result resolution broke.
*/
#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 want_exit;
int stage_mask;
int byte_id; /* assert cs.s == ww.s */
};
static const struct row rows[] = {
{ "field_vs_global_leaf",
"package main;\n"
"type sentinel = void;\n"
"fn pull(x: i64) i64 = {\n"
" return x + 11i64;\n"
"};\n"
"type src = struct {\n"
" pull: fn(s: *src, k: i64) (i64 | sentinel),\n"
"};\n"
"fn srcpull(s: *src, k: i64) (i64 | sentinel) = {\n"
" return k + 100i64;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: src;\n"
" s.pull = srcpull;\n"
" let g: i64 = pull(3i64);\n"
" let r = s.pull(&s, 5i64);\n"
" let out: i64 = -1i64;\n"
" if (r is i64) { out = r as i64; };\n"
" if (out == 105i64 && g == 14i64) { return 42; };\n"
" return 1i32;\n"
"};\n",
42,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the source
* (filed task #15); cstage ww does too. Sweep then rmdir. */
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[1024];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
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[2048];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
/* run_row — build via driver, run the binary, return exit (or -1 on
* build failure). */
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/ffl_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main782");
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;
}
/* 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). Mirror of 783's helper. */
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/ffl_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/ffl_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main782");
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.sepwork/__root.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.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char 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++;
int got = run_row(cdrv, r, seq++);
if (got != r->want_exit) {
fprintf(stderr, "fieldfn_leaf_collide[cs][%s]: exit=%d want=%d\n",
r->label, got, r->want_exit);
fail++;
}
}
if (wwpresent && (r->stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, r, seq++);
if (got != r->want_exit) {
fprintf(stderr, "fieldfn_leaf_collide[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, "fieldfn_leaf_collide[byte-id][%s]: cstage vs wwstage asm differs\n",
r->label);
fail++;
}
}
}
}
if (fail) {
fprintf(stderr, "fieldfn_leaf_collide: %d/%d checks failed\n", fail, total);
return 1;
}
printf("fieldfn_leaf_collide: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,52 +1,26 @@
/*
* 788_type_value_shadow_run — project #225 close. A value binding that
* shadows a same-named TYPE must NOT hide that type in type-annotation
* or cast position. ww keeps type and value namespaces separate;
* wwstage already did, cstage did not.
* 788_type_value_shadow_run -- SLIMMED to the one irreducible asymmetric row.
*
* Pre-fix cstage rejected valid Hare code that wwstage + Hare accept:
* The #225 type/value-namespace corpus migrated to fold-2 homes (#5-C6): the
* value rows (param_and_cast, cast_local) -> test/lang/type_value_shadow_test.ww
* (@test + T2 byte-id).
*
* type off = i64;
* fn f(off: off) i64 = { return off: off; };
* value_not_type_neg CANNOT move to a runww //ww:error carrier: both stages
* REJECT a value name in type position with no same-named type, but with
* DIVERGENT diagnostics -- cstage emits a clean "unknown type 'q'" while wwstage
* trips an internal "asserttyped: cast" guard (filed task #29: a checker-quality
* cs!=ww, wwstage to align DOWN to cstage's clean diagnostic per rule 10). No
* honest shared substring exists, so the #20-non-vacuity ERROR carrier (which
* demands the SAME substring in both stages) is inexpressible. A build-reject
* also emits no .s, so there is no byte-id surface (ken). It stays a slim C pin
* asserting rc!=0 on BOTH stages.
*
* cmd/wcc/check.c resolve_typename resolved the type name via the
* kind-blind scope_lookup_prefer, which returns the first NAME match
* of ANY kind (innermost→outermost). The param `off` (SK_PARAM, inner
* scope) shadowed the global `type off` (SK_TYPE, scope 0): the inner
* value won, resolve_typename saw a non-SK_TYPE → nil → "unknown type
* 'off'". The type binding was present; the lookup just couldn't see
* past the shadowing value.
*
* Fix (additive, cstage only — aligning UP to wwstage per rule 10):
* a kind-filtered scope_lookup_type (cmd/wcc/sym.c) that skips every
* non-SK_TYPE Sym and keeps scanning, returning the innermost SK_TYPE
* of that name. resolve_typename calls it instead of scope_lookup_prefer.
* Both type-annotation (param type) and cast-target (`expr: T`, the
* N_CAST rhs) route through resolve_type→N_TNAME→resolve_typename, so
* the single swap covers both positions.
*
* ROW POLARITY: both rows build + run on BOTH stages and assert
* cs.s == ww.s (rule-10). The fix is byte-id-NEUTRAL by construction —
* the new branch fires only on the pre-fix "unknown type" error path,
* which no passing corpus reaches.
* param_and_cast — the canonical repro: a param named `off` shadows
* `type off`, used both as the param's own type and
* as a cast target inside the body.
* cast_local — a `let` value shadows `type t`, then `v: t` casts
* through the shadowed type name in the same scope.
* value_not_type_neg — the inverse guard: a value name with NO
* same-named type in any scope must still be rejected
* in type position. Proves the kind filter only looks
* PAST a value to a real type, never promotes the
* value itself. Both stages must fail the build.
*
* Not covered (not constructible): a fn-name shadowing a type — fns and
* types share the flat global scope and dedup on (name, mod), so a
* same-named global `fn`/`type` pair is a "duplicate fn" error, never a
* shadow. SK_PARAM + SK_VAR are the only value kinds that can shadow.
*
* GATE: must stay GREEN. Red on cstage means the kind filter regressed;
* red on byte-id means the two stages diverged on the shadow path.
* Non-vacuity self-check (the pin CAN go RED): the SAME name `q` is also built
* as a real TYPE in `valid_src`, which BOTH stages must ACCEPT (rc==0). If the
* harness reported reject for everything, that control would fail; it proves the
* pin discriminates accept-vs-reject, not "all builds error". Mutation-checked:
* giving `value_not_type_neg` a real `type q = i32;` flips both stages to accept
* -> the reject leg goes RED (verified).
*/
#include <stdio.h>
#include <stdlib.h>
@@ -63,150 +37,47 @@ runwait(const char *cmd)
return -1;
}
#define STAGE_CS 1
#define STAGE_WW 2
/* value_not_type_neg: `q` is a VALUE (SK_VAR) in type/cast position with no
* same-named type in any scope -- both stages reject (cstage "unknown type
* 'q'", wwstage asserttyped:cast, #29). */
static const char *reject_src =
"package main;\n"
"export fn main() i32 = {\n"
" let q: i32 = 3;\n"
" return q: q;\n"
"};\n";
struct row {
const char *label;
const char *src;
int want_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "param_and_cast",
"package main;\n"
"type off = i64;\n"
"fn f(off: off) i64 = { return off: off; };\n"
"export fn main() i32 = { return f(5): i32; };\n",
5, STAGE_CS | STAGE_WW, 1 },
{ "cast_local",
"package main;\n"
"type t = i32;\n"
"export fn main() i32 = {\n"
" let t: i32 = 7;\n"
" return t: t;\n"
"};\n",
7, STAGE_CS | STAGE_WW, 1 },
/* NEG: a value in type position with NO same-named type must still
* error. Guards the inverse of the fix — scope_lookup_type must
* never PROMOTE the value `q` (SK_VAR) into a type, only look PAST
* a value to a real type that is genuinely there. want_exit -1 =
* build fails on both stages (cstage "unknown type 'q'"). */
{ "value_not_type_neg",
"package main;\n"
"export fn main() i32 = {\n"
" let q: i32 = 3;\n"
" return q: q;\n"
"};\n",
-1, STAGE_CS | STAGE_WW, 0 },
};
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.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
/* Vacuity control: the SAME name `q` bound as a real TYPE -- both stages accept.
* Proves the pin distinguishes accept from reject. */
static const char *valid_src =
"package main;\n"
"type q = i32;\n"
"export fn main() i32 = {\n"
" return 3: q;\n"
"};\n";
/* build_only -- write `src`, build via `driver` (no run). Returns the build
* exit code (0 = accepted, nonzero = rejected). */
static int
write_source(const char *path, const char *src)
build_only(const char *driver, const char *src, int seq)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
char tmpdir[96], srcp[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tvs_%d_%d", getpid(), seq);
mkdir(tmpdir, 0755);
snprintf(srcp, sizeof srcp, "%s/c.ww", tmpdir);
snprintf(outbin, sizeof outbin, "%s/c", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(srcp, "wb");
if (!f) { runwait(rmcmd); 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];
/* #93 sep layout: `--sep -o <src-stem>` relocates artifacts to
* <stem>.sepwork/; WW_PKGCACHE is pinned under tmpdir so the shared
* out/.pkgcache is untouched. The value_not_type_neg row still fails:
* a checker error makes w6c return nonzero regardless of placement. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
/* run_row — build via driver, run the binary, return exit (or -1 on
* build failure). */
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/tvs_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main788");
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;
}
/* 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/tvs_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/tvs_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main788");
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.sepwork/__root.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.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
snprintf(cmd, sizeof cmd, "timeout 20 %s build -o %s %s >/dev/null 2>&1",
driver, outbin, srcp);
int brc = runwait(cmd);
runwait(rmcmd);
return brc;
}
int
@@ -214,58 +85,45 @@ 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];
char absbin[2080];
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[640], wdrv[640];
char cdrv[2120], wdrv[2120];
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);
int fail = 0, seq = 0;
for (int i = 0; i < n; i++) {
const struct row *r = &rows[i];
/* reject leg: both stages must REJECT (rc!=0). */
if (build_only(cdrv, reject_src, seq++) == 0) {
fprintf(stderr, "type_value_shadow[cs]: value_not_type_neg ACCEPTED (want reject)\n");
fail++;
}
if (wwpresent && build_only(wdrv, reject_src, seq++) == 0) {
fprintf(stderr, "type_value_shadow[ww]: value_not_type_neg ACCEPTED (want reject)\n");
fail++;
}
if (r->stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, r, seq++);
if (got != r->want_exit) {
fprintf(stderr, "type_value_shadow[cs][%s]: exit=%d want=%d\n",
r->label, got, r->want_exit);
fail++;
}
}
if (wwpresent && (r->stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, r, seq++);
if (got != r->want_exit) {
fprintf(stderr, "type_value_shadow[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, "type_value_shadow[byte-id][%s]: cstage vs wwstage asm differs\n",
r->label);
fail++;
}
}
}
/* vacuity control: both stages must ACCEPT (rc==0). */
if (build_only(cdrv, valid_src, seq++) != 0) {
fprintf(stderr, "type_value_shadow[cs]: vacuity control REJECTED (pin may be vacuous)\n");
fail++;
}
if (wwpresent && build_only(wdrv, valid_src, seq++) != 0) {
fprintf(stderr, "type_value_shadow[ww]: vacuity control REJECTED (pin may be vacuous)\n");
fail++;
}
if (fail) {
fprintf(stderr, "type_value_shadow: %d/%d checks failed\n", fail, total);
fprintf(stderr, "type_value_shadow: %d checks failed\n", fail);
return 1;
}
printf("type_value_shadow: %d/%d ok\n", total, total);
printf("type_value_shadow: value_not_type_neg pin ok\n");
return 0;
}

View File

@@ -1,357 +0,0 @@
/*
* 813_arrlit_infer_elem_run — #103 + #108 (+ #104 + the m8_slice
* acceptance gap): an INFERRED let defaults its untyped-int to `int`
* (8B machine word) on BOTH stages, byte-identically and at runtime.
*
* THE BUG (cstage was the truncating side — #263 polarity):
* cstage type_default(TY_UNTYPED_INT) returned ty_i32 (4B). For a
* scalar `let x = <v>` and an inferred array `let a = [<v>, ...]`
* (no annotation) the literal defaulted to i32 — silently TRUNCATING
* any value > 2^31 (5000000000 → 705032704) and striding arrays at 4.
* wwstage kept the element as raw untyped_int (size 0), which sized
* INCONSISTENTLY across cgen consumers: the array STORE strode the 8
* sentinel (so the value width was right) but letslotsize under-
* allocated the frame (SEGV) and cgindex strode the READ at 1 — so
* `let a = [..]` SEGV'd on wwstage and the scalar ran correct only by
* the 8B-store accident. The two stages were each wrong differently.
*
* THE FIX (one root, both stages, FUSE):
* - cstage type.c type_default(TY_UNTYPED_INT) i32 -> int; the empty
* arrlit fallback check.c ty_i32 -> ty_int (symmetric, count-0
* neutral). int = machine word = 8B (Go-style; MEMORY
* project_int_machine_word_derived_limits) — Hare lowers a flexible
* iconst to `int`, never a fixed i32 (ref/harec/src/types.c:835).
* - wwstage check.ww exprtype N_ARRLIT: default the inferred
* element's UNTYPED flavor to its concrete type (untyped_int->int,
* _float->f64, _str->str, _rune->rune, _bool->bool), the empty-elt
* fallback "i32"->"int", and stamp the synthesized N_TARRAY's
* .type_ — so slotsize / elemsizeofc / letslotsize all read its
* real [N]int size (32 for [4]int), routing through the type table
* (rule-13) instead of the nil/0/sentinel fallbacks. SSoT — no
* letslotsize special-case.
*
* POLARITY TEETH: the wide rows use 5000000000 ( > 2^31). A small-value
* row would pass both stages by luck (gate-blind); only a > 2^31 value
* distinguishes the truncating i32 default from the correct int.
*
* COVERAGE — the two direct repros plus the FIVE corpus movers ken's v2
* re-census named (each converges 0/0 byte-id + run-correct, NONE
* both-wrong): m2_while (#108 scalar, alias-bool loop), m8_range1/2
* (#104 for-range elem over alias / 2-level-alias array), m8_slice1/2
* (#103 SEGV + slice-init acceptance over alias / 2-level-alias slice).
* Plus the annotated controls [4]i32 (stride-4) and [4]int (stride-8),
* which the fix must leave byte-identical — it touches ONLY the inferred
* untyped default, never an explicit element type.
*
* row | shape | want
* -----------------+---------------------------------------------+-----
* arr_wide | let a=[5e9,2,3,4]; a[0]==5e9 && a[3]==4 | 0
* scalar_wide | let x=5e9; x==5e9 (#108 teeth) | 0
* arr_small | let a=[10,20,30,40]; a[2] | 30
* m2_while | #108 scalar via alias-bool loop counter | 0
* m8_range1 | #104 for-range elem over alias [4]int | 0
* m8_range2 | #104 for-range elem over 2-level-alias | 0
* m8_slice1 | #103 inferred a + slice s:sl=a[1:3] | 0
* m8_slice2 | #103 + 2-level-alias slice + re-slice | 0
* ctrl_i32 | let a:[4]i32=[1,2,3,4]; a[3] (stride-4) | 4
* ctrl_int | let a:[4]int=[1,2,3,4]; a[3] (stride-8) | 4
*
* EACH ROW CARRIES THREE DIMENSIONS (684/802 model):
* (a) cstage `ww build` + run, asserting the exit — pins the converged
* asm is runtime-correct (real values, no truncation/SEGV).
* (b) wwstage `ww_ww build` + run (gated on w6c_ww) — pins the ww SEGV
* is gone and the value round-trips.
* (c) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
*
* GATE POLARITY: must stay GREEN. A wrong exit means the inferred
* default regressed to a truncating/under-strided type; a byte-id FAIL
* means the stages diverged.
*/
#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;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
/* #103 array, > 2^31 element — truncation + frame teeth. */
{ "arr_wide",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a = [5000000000, 2, 3, 4];\n"
"\tif (a[0] != 5000000000) { return 1; };\n"
"\tif (a[3] != 4) { return 2; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* #108 scalar, > 2^31 — the cstage general truncation teeth. */
{ "scalar_wide",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet x = 5000000000;\n"
"\tif (x != 5000000000) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* basic inferred array — frame + stride sanity (mutation: a wrong
* stride/slot reads a neighbouring word, not 30). */
{ "arr_small",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a = [10, 20, 30, 40];\n"
"\treturn a[2]: i32;\n"
"};\n",
30 },
/* corpus mover m2_while (#108 scalar via an alias-bool loop). */
{ "m2_while",
"package main;\n"
"type myb = bool;\n"
"export fn main() i32 = {\n"
"\tlet b: myb = true;\n"
"\tlet i = 0;\n"
"\tfor (b) {\n"
"\t\ti = i + 1;\n"
"\t\tif (i >= 3) { b = false; };\n"
"\t};\n"
"\tif (i != 3) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* corpus mover m8_range1 (#104 for-range elem over alias array). */
{ "m8_range1",
"package main;\n"
"type arr = [4]int;\n"
"export fn main() i32 = {\n"
"\tlet a: arr = [1, 2, 3, 4];\n"
"\tlet sum = 0;\n"
"\tfor (let x .. a) {\n"
"\t\tsum = sum + x;\n"
"\t};\n"
"\tif (sum != 10) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* corpus mover m8_range2 (#104 for-range elem over 2-level alias). */
{ "m8_range2",
"package main;\n"
"type arr = [4]int;\n"
"type arr2 = arr;\n"
"export fn main() i32 = {\n"
"\tlet a: arr2 = [1, 2, 3, 4];\n"
"\tlet sum = 0;\n"
"\tfor (let x .. a) {\n"
"\t\tsum = sum + x;\n"
"\t};\n"
"\tif (sum != 10) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* corpus mover m8_slice1 (#103 inferred array + alias-slice init). */
{ "m8_slice1",
"package main;\n"
"type sl = []int;\n"
"export fn main() i32 = {\n"
"\tlet a = [10, 20, 30, 40];\n"
"\tlet s: sl = a[1:3];\n"
"\tif (s.len != 2) { return 1; };\n"
"\tif (s[0] != 20) { return 2; };\n"
"\tif (s[1] != 30) { return 3; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* corpus mover m8_slice2 (#103 + 2-level-alias slice + re-slice). */
{ "m8_slice2",
"package main;\n"
"type sl = []int;\n"
"type sl2 = sl;\n"
"export fn main() i32 = {\n"
"\tlet a = [10, 20, 30, 40];\n"
"\tlet s: sl2 = a[1:3];\n"
"\tif (s.len != 2) { return 1; };\n"
"\tif (s[0] != 20) { return 2; };\n"
"\tlet t = s[0:1];\n"
"\tif (t[0] != 20) { return 3; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* CONTROL: annotated [4]i32 — must stay i32/stride-4, byte-id,
* UNTOUCHED by the inferred-default fix. */
{ "ctrl_i32",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]i32 = [1, 2, 3, 4];\n"
"\treturn a[3];\n"
"};\n",
4 },
/* CONTROL: annotated [4]int — int/stride-8, byte-id. */
{ "ctrl_int",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]int = [1, 2, 3, 4];\n"
"\treturn a[3]: i32;\n"
"};\n",
4 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/aie_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/aie_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/aie_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[72], ws[72], cmd[1024];
snprintf(src, sizeof src, "/tmp/aie_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/aie_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/aie_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); 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(src); unlink(cs); unlink(ws);
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[1100], wdrv[1100];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arrlit_infer_elem: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arrlit_infer_elem[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"arrlit_infer_elem: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arrlit_infer_elem: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,226 +0,0 @@
/*
* 911_continue_run — runtime + byte-id net for #138: `continue` in a
* loop with a post-step (3-clause C-style `for (init; cond; post)` OR
* Hare-range `for (let x .. xs)` with its implicit `i+=1`) must run
* the post-step BEFORE re-testing the cond / bound. Pre-fix the
* continue-target was the loop top, which SKIPPED the post-step → the
* value that triggered continue never advanced → infinite loop.
*
* Pre-fix BOTH STAGES emitted identical buggy asm (`JMP loop_top` for
* continue, post-step JMP'd over) — cs==ww byte-id held → 990-997 gate
* was BLIND to the miscompile. Found by impl-strconv-fold2 during the
* fold-3 decimal.ha port: leftshift_newdigits uses `for (let i: u32 =
* 0u32; i < n; i += 1u32) { ... else if (d.digits[i]==p5[i]) continue;
* ... }`; pre-fix that path infinite-looped at the first equal digit.
*
* Fix (both stages): allocate a dedicated `post` (3-clause) / `rpost`
* (range) label; continue jumps to that label; the label emits before
* the post-step; fall-through naturally hits it too. For 1-clause `for
* (cond)` with no post-step, the cont-target stays = loop-top
* (unchanged from pre-#138, byte-id preserved on the corpus's
* 1-clause shape).
*
* Rows cover the 3 shapes the fix targets PLUS a 1-clause regression
* row asserting the byte-id surface for the 1-clause path is
* unchanged. cstage `ww build` + run for runtime; w6c vs w6c_ww `.s`
* cmp for rule-10 byte-id.
*/
#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;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* The lead's repro: 3-clause for with continue. Pre-fix infinite
* loop on i=2; post-fix count=4 (i=0,1,3,4 all increment count). */
{ "for3_skip_one",
"package main;\n"
"export fn main() i32 = {\n"
" let count: i32 = 0;\n"
" for (let i: i32 = 0; i < 5; i += 1) {\n"
" if (i == 2) { continue; };\n"
" count += 1;\n"
" };\n"
" return count;\n"
"};\n", 4 },
/* Two skips: continue must advance i correctly each time. count
* over i=0,2,4 only (skip 1 and 3); 3 hits × 10 = 30. */
{ "for3_skip_two",
"package main;\n"
"export fn main() i32 = {\n"
" let c: i32 = 0;\n"
" for (let i: i32 = 0; i < 5; i += 1) {\n"
" if (i == 1) { continue; };\n"
" if (i == 3) { continue; };\n"
" c += 10;\n"
" };\n"
" return c;\n"
"};\n", 30 },
/* Hare-range form `for (let x .. xs)`. Continue must run the
* implicit `i+=1` increment. Pre-fix infinite-loop on first
* matching element; post-fix sum over non-skipped values. xs =
* [10, 20, 30, 40, 50]; skip 30; sum = 10+20+40+50 = 120. */
{ "range_skip",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: [5]i32 = [10, 20, 30, 40, 50];\n"
" let sum: i32 = 0;\n"
" for (let v .. xs) {\n"
" if (v == 30) { continue; };\n"
" sum += v;\n"
" };\n"
" return sum;\n"
"};\n", 120 },
/* 1-clause `for (cond)` with continue. No post-step, so cont-
* target stays = loop-top. Pre-#138 emission must be byte-id
* preserved — this is the bootstrap shape. Manual post-step
* inside the body. count over i=0,1,3,4 (skip i=2) → 4. */
{ "for1_continue_byteid",
"package main;\n"
"export fn main() i32 = {\n"
" let count: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < 5) {\n"
" let cur: i32 = i;\n"
" i += 1;\n"
" if (cur == 2) { continue; };\n"
" count += 1;\n"
" };\n"
" return count;\n"
"};\n", 4 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
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 w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "continue: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwcont_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[128];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwcont_%d_%d.ww",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwcont_%d_%d",
tmpdir, getpid(), i);
/* Build with 5s timeout — pre-fix the buggy rows infinite-
* looped; post-fix all rows must exit cleanly under it. */
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
/* timeout wrapper: pre-fix bug = infinite loop; want clean
* exit. exit 124 = timeout/hang. */
char rcmd[256];
snprintf(rcmd, sizeof rcmd, "timeout 5 %s", outbin);
int got = runwait(rcmd);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d "
"(124 = timeout / pre-fix infinite loop)\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwcont_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwcont_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d continue tests failed\n", fail, n);
return 1;
}
printf("continue: %d/%d ok (cstage run + cs==ww byte-id)\n",
n, n);
return 0;
}

View File

@@ -1,341 +0,0 @@
/*
* 937_forrange_fieldbase_run — #70: by-value for-range over a NON-IDENT
* slice/str base (field chain, indexed element, call result). Pre-#70
* the range header stored cgexpr's AX — the DATA POINTER (slice cgexpr
* leaves AX=ptr, BX=len, CX=cap) — into the single bound temp, and the
* per-iteration code had no non-ident base arm, so the bound reload
* doubled as the base: i was compared against the POINTER and the loop
* walked off the end (regex.finish, SEGV on the first non-empty
* charsets; empty slices coincidentally exited on ptr==0 — latent since
* fold 1, BYTE-ID both stages, so the 989 M_ID gate held on
* both-wrong-identical). Now: bound = len, base ptr spilled to its own
* .rgb slot. Deref bases (*p — the #11 family, cgexpr does NOT deliver
* the header) and non-ident ARRAY bases stay LOUD.
*
* row | shape | want
* -------------------+----------------------------------------+-----
* field_base | h.xs ([]i64) value + ptr root | 0
* field_base_24B | p.names ([]str) — the finish() shape | 0
* indexed_base | m[0] ([][]i64 element) | 0
* call_base | mk() ([]i64 call result) | 0
* empty_field | zero header field exits cleanly | 0
* eval_once | base reassigned INSIDE the loop — |
* | header captured once (a per-iteration |
* | re-read of the field would mis-sum) | 0
* reject_deref | *q base stays loud (#11) | err
* reject_arr_field | h.arr ([3]i64 field) stays loud | err
*
* Every K_RUN row also asserts cstage/wwstage asm byte-id. NNN<950,
* self-contained (/tmp, no imports).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
/* errlog_has — a BUILDERR row must fail WITH its diagnostic; any other
* failure (parse error, crash) is a vacuous reject (940 precedent). */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static const struct row rows[] = {
{ "field_base",
"package main;\n"
"type holder = struct { xs: []i64, name: str };\n"
"fn mk() []i64 = {\n"
" let xs: []i64;\n"
" append(xs, 7);\n"
" append(xs, 8);\n"
" return xs;\n"
"};\n"
"export fn main() i32 = {\n"
" let h: holder = holder { xs = mk(), name = \"h\" };\n"
" let s1: i64 = 0;\n"
" for (let v .. h.xs) { s1 += v; };\n"
" if (s1 != 15) { return 1; };\n"
" let p: *holder = &h;\n"
" let s2: i64 = 0;\n"
" for (let v .. p.xs) { s2 += v; };\n"
" if (s2 != 15) { return 2; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* 24B str-header elements through a ptr field — regex.finish's
* exact shape (the live SEGV repro). */
{ "field_base_24B",
"package main;\n"
"type bag = struct { names: []str, n: i64 };\n"
"export fn main() i32 = {\n"
" let names: []str;\n"
" append(names, \"ab\");\n"
" append(names, \"cde\");\n"
" let b: bag = bag { names = names, n = 2 };\n"
" let p: *bag = &b;\n"
" let total: i64 = 0;\n"
" for (let nm .. p.names) {\n"
" total += (len(nm): i64);\n"
" };\n"
" if (total != 5) { return 1; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* indexed element base — the task-#57 shape, graduated by #70 */
{ "indexed_base",
"package main;\n"
"fn mk() []i64 = {\n"
" let xs: []i64;\n"
" append(xs, 7);\n"
" append(xs, 8);\n"
" return xs;\n"
"};\n"
"export fn main() i32 = {\n"
" let m: [][]i64;\n"
" append(m, mk());\n"
" let s: i64 = 0;\n"
" for (let v .. m[0]) { s += v; };\n"
" if (s != 15) { return 1; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
{ "call_base",
"package main;\n"
"fn mk() []i64 = {\n"
" let xs: []i64;\n"
" append(xs, 7);\n"
" append(xs, 8);\n"
" return xs;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: i64 = 0;\n"
" for (let v .. mk()) { s += v; };\n"
" if (s != 15) { return 1; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* zero header: the pre-#70 code exited only because ptr==0; the
* fixed code must exit because len==0. */
{ "empty_field",
"package main;\n"
"type holder = struct { xs: []i64, name: str };\n"
"export fn main() i32 = {\n"
" let h: holder = holder { name = \"e\", ... };\n"
" for (let v .. h.xs) { return 1; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* the range header (base ptr + len) is captured ONCE at loop
* entry — Hare evaluates the range operand once. A fix that
* re-read the field per iteration would walk b (sum 7+100) and
* never see the reassign-then-original discipline. */
{ "eval_once",
"package main;\n"
"type holder = struct { xs: []i64, n: i64 };\n"
"export fn main() i32 = {\n"
" let a: []i64;\n"
" append(a, 7);\n"
" append(a, 8);\n"
" let b: []i64;\n"
" append(b, 100);\n"
" let h: holder = holder { xs = a, n = 0 };\n"
" let s: i64 = 0;\n"
" for (let v .. h.xs) {\n"
" s += v;\n"
" h.xs = b;\n"
" };\n"
" if (s != 15) { return 1; };\n"
" if (len(h.xs) != 1) { return 2; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* deref base: cgexpr on *p does not deliver the AX/BX/CX header
* (the #11 deref-spine family) — pre-#70 it crashed or mis-summed
* SILENTLY; now loud both stages. */
{ "reject_deref",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i64;\n"
" append(xs, 1);\n"
" let q: *[]i64 = &xs;\n"
" let s: i64 = 0;\n"
" for (let v .. *q) { s += v; };\n"
" return (s: i32);\n"
"};\n", 0,
K_BUILDERR, "for-range over a deref base unwired (#11)" },
/* non-ident ARRAY base: no base spill and a different cgexpr
* register shape — loud (rule 7) until a consumer wires it. */
{ "reject_arr_field",
"package main;\n"
"type holder = struct { arr: [3]i64, n: i64 };\n"
"export fn main() i32 = {\n"
" let h: holder = holder { arr = [1, 2, 3], n = 0 };\n"
" let s: i64 = 0;\n"
" for (let v .. h.arr) { s += v; };\n"
" return (s: i32);\n"
"};\n", 0,
K_BUILDERR, "for-range over a non-ident array base unwired (#70)" },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tfr_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tfr_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tfr_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/tfr_%d_e_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || errlog_has(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud builderr "
"\"%s\" (brc=%d)\n", r->label, driver,
r->experr ? r->experr : "", brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/tfr_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/tfr_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/tfr_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
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[2120], wdrv[2120];
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;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
if (rows[i].kind == K_BUILDERR)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "forrange_fieldbase: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("forrange_fieldbase: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,247 +0,0 @@
/*
* 946_append_structlit_evalorder_run — task #59 (#50's eval-order kin):
* an append/insert of a STRUCT-LITERAL value used to fill the literal's
* fields into the destination slot AFTER cg_append_grow had already bumped
* xs.len. So a field expression that reads `len(xs)` saw the POST-grow
* length. Hare evaluates the value BEFORE the grow.
*
* #263 both-wrong-IDENTICAL: cstage and wwstage emitted byte-identical asm
* that was wrong on BOTH (exit 6 vs the correct 3) — the asm-diff and
* byte-id gates are BLIND to it, so a RUNTIME row is the only net.
*
* Fix (both stages, byte-identical per rule 10): fill the struct literal
* into a fresh per-SITE scratch BEFORE the grow (mirror #50's tagged arm),
* then grow, slot, raw-copy scratch->slot. insert() desugars in-place to
* append and re-dispatches into THIS exact arm, so it is fixed by
* construction — R2 proves the desugar twin (both stages).
*
* Rows assert RUNTIME on BOTH drivers AND cs==ww .s byte-identical:
* append_eval `append(xs, rec{f=len(xs):i64})` x3 -> sum == 3 (pre-grow);
* base bug = 6 (post-grow), main returns 1.
* insert_eval `insert(xs[1], rec{f=len(xs):i64})` -> inserted.f == 2
* (pre-grow len at insert time); base bug = 3.
*
* All K_RUN: build+run exit 0 on BOTH drivers AND cs==ww byte-identical.
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent).
*/
#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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "append_eval",
"package main;\n"
"type rec = struct { f: i64 };\n"
"export fn main() i32 = {\n"
" let xs: []rec = [];\n"
" append(xs, rec{ f = len(xs): i64 });\n"
" append(xs, rec{ f = len(xs): i64 });\n"
" append(xs, rec{ f = len(xs): i64 });\n"
" let s: i64 = xs[0].f + xs[1].f + xs[2].f;\n"
" if (s != 3) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "insert_eval",
"package main;\n"
"type rec = struct { f: i64 };\n"
"export fn main() i32 = {\n"
" let xs: []rec = [];\n"
" append(xs, rec{ f = 100 });\n"
" append(xs, rec{ f = 200 });\n"
" insert(xs[1], rec{ f = len(xs): i64 });\n"
" if (xs[1].f != 2) { return 1; };\n"
" if (xs[0].f != 100) { return 2; };\n"
" if (xs[2].f != 200) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
/*
* narrow_neighbor — a sub-8B struct (`{a: i32}`, esz=4, packs at
* stride 4) appended to fill the slice exactly to capacity. Catches
* BOTH halves of #59 in one row:
* pre-fix (pristine 39432f7): field exprs eval post-grow → sum
* wrong → returns 1.
* 8B-block-copy fix (the over-copy regression): eval-order is
* correct but the last slot's 8-byte copy writes 4 bytes PAST
* the 32-byte buffer into the adjacent `victim` allocation →
* victim[0] clobbered → returns 2.
* precise 8/4/2/1 ladder: both correct → 0.
* The 8-element fill makes len==cap==8 so the tail over-write lands
* past the buffer; `victim` is alloc'd right after xs's buffer (bump
* allocator) so the clobber is observable.
*/
{ "narrow_neighbor",
"package main;\n"
"type rec = struct { a: i32 };\n"
"export fn main() i32 = {\n"
" let xs: []rec = [];\n"
" append(xs, rec{ a = len(xs): i32 });\n"
" let victim: []i64 = [];\n"
" append(victim, 1234605616436508552i64);\n"
" let i: i32 = 1;\n"
" for (i < 8) {\n"
" append(xs, rec{ a = len(xs): i32 });\n"
" i += 1;\n"
" };\n"
" let s: i32 = 0;\n"
" let j: i32 = 0;\n"
" for (j < 8) { s += xs[j].a; j += 1; };\n"
" if (s != 28) { return 1; };\n"
" if (victim[0] != 1234605616436508552i64) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], errf[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ase_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ase_%d_%d.ww", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/ase_%d_e_%d", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ase_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/ase_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/ase_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/ase_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src);
if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c failed\n", r->label); rc = 1; }
else {
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src);
if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c_ww failed\n", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(rule-10 byte-id)\n", r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; int gated; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "append_structlit_evalorder: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "append_structlit_evalorder: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("append_structlit_evalorder: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,242 +0,0 @@
/*
* 949_nullable_assert_run — the `e as T` type assertion on a nullable
* `(*T | void)` operand must discriminate POINTER-vs-NULL, not compare
* the pointer value against a variant tag index (report item #17, F4).
*
* Gate-blind hazard: for a nullable operand the slot word IS the
* pointer (no tag word), so pre-fix wwstage cgtypeassert emitted
* `CMPQ $want,AX; JE ok` against the POINTER and then unwrapped a frame
* word past the 8B slot — a valid pointer aborted, a void value
* silently PASSED, and the unwrap read garbage. cstage already carried
* the nullable arm (cmd/w6c/cgen.c N_TYPEASSERT) and ww's own
* cgtypetest carries the matching nullable fold; the fix mirrors that
* arm into cgtypeassert. wwstage-only (cstage correct) → after the fix
* cs.s == ww.s, so the byte-id row witnesses rule-10 convergence.
*
* Two assertions per row:
* - RUNTIME: build with `ww` (cstage) and `ww_ww` (wwstage), run,
* compare exit code. This is what was wrong pre-fix.
* - ASM BYTE-ID: compile the same source through `w6c` and `w6c_ww`
* and require byte-identical .s (rule 10).
*
* Rows:
* 1. asrt_ptr_ok — valid ptr `as *int` must unwrap and deref.
* Pre-fix wwstage aborted exit(1) (CMPQ against the pointer).
* 2. asrt_void_fail — void value `as *int` must abort exit(1).
* Pre-fix wwstage silently PASSED (null == ptr-tag 0).
* 3. asrt_void_target_fail — valid ptr `as void` must abort exit(1)
* (non-null != the void variant). Exercises the void polarity.
* 4. asrt_void_target_ok — void value `as void` must succeed.
* Exercises the void polarity on the matching side.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "asrt_ptr_ok",
"package main;\n"
"export fn main() i32 = {\n"
" let x: int = 42;\n"
" let p: (*int|void) = &x;\n"
" let q: *int = p as *int;\n"
" return (*q): i32;\n"
"};\n",
42 },
{ "asrt_void_fail",
"package main;\n"
"export fn main() i32 = {\n"
" let p: (*int|void) = void;\n"
" let q = p as *int;\n"
" return 50;\n"
"};\n",
1 },
{ "asrt_void_target_fail",
"package main;\n"
"export fn main() i32 = {\n"
" let x: int = 5;\n"
" let p: (*int|void) = &x;\n"
" let z = p as void;\n"
" return 7;\n"
"};\n",
1 },
{ "asrt_void_target_ok",
"package main;\n"
"export fn main() i32 = {\n"
" let p: (*int|void) = void;\n"
" let z = p as void;\n"
" return 7;\n"
"};\n",
7 },
};
static int
write_src(const char *dir, const char *base, const struct row *r, char *out,
size_t outsz)
{
snprintf(out, outsz, "%s/%s.ww", dir, base);
FILE *f = fopen(out, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
return 0;
}
static int
build_run(const char *driver, const char *src, const char *workdir)
{
char cmd[8192];
snprintf(cmd, sizeof cmd, "cd %s && %s build %s > /dev/null 2>&1",
workdir, driver, src);
if (runwait(cmd) != 0) return -1;
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[1024];
snprintf(outbin, sizeof outbin, "%s/%s", workdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
return runwait(outbin);
}
static int
files_equal(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int ca, cb, eq = 1;
do {
ca = fgetc(fa);
cb = fgetc(fb);
if (ca != cb) { eq = 0; break; }
} while (ca != EOF);
fclose(fa);
fclose(fb);
return eq;
}
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], cw6[640], ww6[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(cw6, sizeof cw6, "%s/w6c", bin);
snprintf(ww6, sizeof ww6, "%s/w6c_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int have_w6cww = (access(ww6, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
const struct row *r = &rows[i];
char dir[] = "/tmp/nasr.XXXXXX";
if (mkdtemp(dir) == NULL) {
fprintf(stderr, "row[%s]: mkdtemp failed\n", r->label);
fail++; total++;
continue;
}
char src[1024];
if (write_src(dir, "p", r, src, sizeof src) != 0) {
fprintf(stderr, "row[%s]: write src failed\n", r->label);
fail++; total++;
goto cleanup;
}
if (have_w6cww) {
char css[1024], wss[1024], cmd[8192];
snprintf(css, sizeof css, "%s/cs.s", dir);
snprintf(wss, sizeof wss, "%s/ww.s", dir);
snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1",
cw6, css, src);
int rc1 = runwait(cmd);
snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1",
ww6, wss, src);
int rc2 = runwait(cmd);
total++;
if (rc1 != 0 || rc2 != 0) {
fprintf(stderr,
"row[%s]: w6c/w6c_ww emit failed (%d/%d)\n",
r->label, rc1, rc2);
fail++;
} else if (files_equal(css, wss) != 1) {
fprintf(stderr,
"row[%s]: cs.s != ww.s (rule-10 break)\n",
r->label);
fail++;
}
}
{
char wk[1024];
snprintf(wk, sizeof wk, "%s/cs", dir);
mkdir(wk, 0755);
int got = build_run(cdrv, src, wk);
total++;
if (got != r->want) {
fprintf(stderr,
"row[%s][cstage]: exit=%d want=%d\n",
r->label, got, r->want);
fail++;
}
}
if (have_ww) {
char wk[1024];
snprintf(wk, sizeof wk, "%s/ww", dir);
mkdir(wk, 0755);
int got = build_run(wdrv, src, wk);
total++;
if (got != r->want) {
fprintf(stderr,
"row[%s][wwstage]: exit=%d want=%d\n",
r->label, got, r->want);
fail++;
}
}
cleanup:
{
char rm[1100];
snprintf(rm, sizeof rm, "rm -rf %s", dir);
runwait(rm);
}
}
if (fail) {
fprintf(stderr,
"nullable_assert_run: %d/%d checks failed\n", fail, total);
return 1;
}
printf("nullable_assert_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,246 +0,0 @@
/*
* 949_nullable_try_run — the `?` / `!` try operators on a nullable
* `(*T | void)` operand must discriminate POINTER-vs-NULL, not
* tag-vs-success-index (report item #15, F4).
*
* Gate-blind hazard: for a nullable operand AX holds the POINTER and
* successtag()=0, so pre-fix wwstage cgtryprop/cgtryunw emitted
* `CMPQ $0,AX; JE ok` — treating NULL as success and a valid pointer
* as the error, exactly inverted. A valid pointer was early-propagated
* / aborted; a null pointer fell through to a deref (SEGV). cstage
* already carried the nullable arm (cmd/w6c/cgen.c N_TRYPROP/N_TRYUNW)
* and ww's own cgtypetest carries the matching nullable fold; the fix
* mirrors that arm into cgtryprop/cgtryunw. wwstage-only (cstage
* correct) → after the fix cs.s == ww.s, so the byte-id row witnesses
* rule-10 convergence.
*
* Two assertions per row:
* - RUNTIME: build with `ww` (cstage) and `ww_ww` (wwstage), run,
* compare exit code. This is what was wrong pre-fix.
* - ASM BYTE-ID: compile the same source through `w6c` and `w6c_ww`
* and require byte-identical .s (rule 10).
*
* Rows:
* 1. tryprop_valid — `p?` on a VALID pointer must unwrap and continue
* (mutate through it). Pre-fix wwstage early-propagated → 50.
* 2. tryprop_null — `p?` on NULL must propagate (caller sees void).
* Pre-fix wwstage treated null as success → null deref SEGV.
* 3. tryunw_valid — `p!` on a VALID pointer must unwrap. Pre-fix
* wwstage aborted exit(1).
* 4. tryunw_null — `p!` on NULL must abort exit(1). Pre-fix wwstage
* treated null as success → null deref SEGV.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "tryprop_valid",
"package main;\n"
"fn g(p: (*int|void)) (*int|void) = { let q = p?; *q = 7; return q; };\n"
"export fn main() i32 = {\n"
" let x: int = 1;\n"
" let r = g(&x);\n"
" if (x != 7) { return 50; };\n"
" return 0;\n"
"};\n",
0 },
{ "tryprop_null",
"package main;\n"
"fn g(p: (*int|void)) (*int|void) = { let q = p?; *q = 99; return q; };\n"
"export fn main() i32 = {\n"
" let r = g(void);\n"
" match (r) {\n"
" case let q: *int => return 60;\n"
" case void => return 7;\n"
" };\n"
"};\n",
7 },
{ "tryunw_valid",
"package main;\n"
"fn h(p: (*int|void)) int = { let q = p!; return *q; };\n"
"export fn main() i32 = {\n"
" let x: int = 42;\n"
" return h(&x): i32;\n"
"};\n",
42 },
{ "tryunw_null",
"package main;\n"
"fn h(p: (*int|void)) int = { let q = p!; return *q; };\n"
"export fn main() i32 = {\n"
" let r = h(void);\n"
" return 50;\n"
"};\n",
1 },
};
static int
write_src(const char *dir, const char *base, const struct row *r, char *out,
size_t outsz)
{
snprintf(out, outsz, "%s/%s.ww", dir, base);
FILE *f = fopen(out, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
return 0;
}
static int
build_run(const char *driver, const char *src, const char *workdir)
{
char cmd[8192];
snprintf(cmd, sizeof cmd, "cd %s && %s build %s > /dev/null 2>&1",
workdir, driver, src);
if (runwait(cmd) != 0) return -1;
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[1024];
snprintf(outbin, sizeof outbin, "%s/%s", workdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
return runwait(outbin);
}
static int
files_equal(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int ca, cb, eq = 1;
do {
ca = fgetc(fa);
cb = fgetc(fb);
if (ca != cb) { eq = 0; break; }
} while (ca != EOF);
fclose(fa);
fclose(fb);
return eq;
}
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], cw6[640], ww6[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(cw6, sizeof cw6, "%s/w6c", bin);
snprintf(ww6, sizeof ww6, "%s/w6c_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int have_w6cww = (access(ww6, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
const struct row *r = &rows[i];
char dir[] = "/tmp/ntry.XXXXXX";
if (mkdtemp(dir) == NULL) {
fprintf(stderr, "row[%s]: mkdtemp failed\n", r->label);
fail++; total++;
continue;
}
char src[1024];
if (write_src(dir, "p", r, src, sizeof src) != 0) {
fprintf(stderr, "row[%s]: write src failed\n", r->label);
fail++; total++;
goto cleanup;
}
if (have_w6cww) {
char css[1024], wss[1024], cmd[8192];
snprintf(css, sizeof css, "%s/cs.s", dir);
snprintf(wss, sizeof wss, "%s/ww.s", dir);
snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1",
cw6, css, src);
int rc1 = runwait(cmd);
snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1",
ww6, wss, src);
int rc2 = runwait(cmd);
total++;
if (rc1 != 0 || rc2 != 0) {
fprintf(stderr,
"row[%s]: w6c/w6c_ww emit failed (%d/%d)\n",
r->label, rc1, rc2);
fail++;
} else if (files_equal(css, wss) != 1) {
fprintf(stderr,
"row[%s]: cs.s != ww.s (rule-10 break)\n",
r->label);
fail++;
}
}
{
char wk[1024];
snprintf(wk, sizeof wk, "%s/cs", dir);
mkdir(wk, 0755);
int got = build_run(cdrv, src, wk);
total++;
if (got != r->want) {
fprintf(stderr,
"row[%s][cstage]: exit=%d want=%d\n",
r->label, got, r->want);
fail++;
}
}
if (have_ww) {
char wk[1024];
snprintf(wk, sizeof wk, "%s/ww", dir);
mkdir(wk, 0755);
int got = build_run(wdrv, src, wk);
total++;
if (got != r->want) {
fprintf(stderr,
"row[%s][wwstage]: exit=%d want=%d\n",
r->label, got, r->want);
fail++;
}
}
cleanup:
{
char rm[1100];
snprintf(rm, sizeof rm, "rm -rf %s", dir);
runwait(rm);
}
}
if (fail) {
fprintf(stderr,
"nullable_try_run: %d/%d checks failed\n", fail, total);
return 1;
}
printf("nullable_try_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,278 +0,0 @@
/*
* 951_arrlit_elem_narrow_run — #251: narrow int/rune array-literal
* elements to a declared [N]T element type, at the let / def /
* struct-field init sites that #130 (test 920) left unwired.
*
* Pre-#251 state (verified by building both stages):
* - cstage REJECTED in-range bare-int/rune array lits at the LOCAL
* let, def, and struct-field sites ("init [4]i32 not assignable to
* declared [4]u8"): the array-literal element type came from the
* elements via type_default (int-lit→i32, rune-lit→rune) with no
* declared-element-type propagation. Only the MODULE-level let site
* (#130) accepted.
* - wwstage was UNEVEN: local/module let correct, but the DEF path ran
* NO init-assignability check and the N_STRUCTLIT head-stamp left
* field assignability parked (#23) — so def + struct-field SILENTLY
* over-accepted out-of-range and str→u8 elements (a rule-7
* miscompile: out-of-range truncates).
*
* #251 fix = the int/rune analogue of coerce_floatlit, realised as the
* EXISTING #130 accept-if-fits range-check (NOT a node-type restamp —
* cgen drives the element WIDTH from the DECLARED type, so a restamp
* would be dead code). Both stages now, at let/def/struct-field:
* - foldable int/rune literal → defcastfits range-check vs declared T
* (in-range accept; out-of-range REJECT loud, rule-7/Drew).
* - non-foldable → type_assignable / isassignable to the element type.
* - cstage: arrlit_init_fits wired at check.c clet (local let),
* struct-field-init, and def-init.
* - wwstage: checkarrlitfits (factored from checkletassign's inline
* #130 block) called from the let path, the def path, and a TARGETED
* array-field walk in the N_STRUCTLIT arm (the array-field
* accept-if-fits ONLY — isolated from the parked #23 field walk).
*
* Element width is declared-type-driven at cgen, so the array literal
* node keeps its [N]i32 / [N]rune type and no restamp is needed; the
* cs==ww byte-id gate confirms the emitted bytes are u8-wide regardless.
*
* Accept rows (run + cs==ww byte-id), int and rune, across the 3 sites:
* - let_int `let a:[4]u8=[65,66,67,68]; a[0]` → 65
* - let_rune `let a:[4]u8=['A','B','C','D']; a[0]` → 65
* - def_int `def D:[4]u8=[65,..]; D[0]` → 65
* - def_rune `def D:[4]u8=['A',..]; D[0]` → 65
* - struct_int `enc{m=[65,..]}; E.m[0]` → 65
* - struct_rune `enc{m=['A',..]}; E.m[1]` → 66
*
* Reject rows (must FAIL build on BOTH stages — rule-7 loud reject).
* Two reject branches per the predicate: the FOLDABLE range-check
* (out-of-range int) and the NON-FOLDABLE element-type check (str→u8),
* each exercised at all 3 sites. (A rune>u8 over-range row is not
* expressible: the ww lexer caps rune escapes at \xFF=255 and does not
* decode multi-byte UTF-8 in a rune literal, so every rune literal
* already fits u8 — the foldable branch is identical for int and rune.)
* - let_over `let a:[2]u8=[300,1]` (foldable: out of range)
* - def_over `def D:[2]u8=[300,1]` (foldable: was a ww over-accept)
* - struct_over `enc{m=[300,1]}` (foldable: was a ww over-accept)
* - let_str `let a:[2]u8=["x","y"]` (non-foldable: str→u8)
* - def_str `def D:[2]u8=["x","y"]` (non-foldable: was a ww over-accept)
* - struct_str `enc{m=["x","y"]}` (non-foldable: was a ww over-accept)
*/
#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;
}
struct arow { const char *label; const char *src; int want_exit; };
struct rrow { const char *label; const char *src; };
static const struct arow accept_rows[] = {
{ "let_int",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]u8 = [65, 66, 67, 68];\n"
" return a[0]: i32;\n"
"};\n", 65 },
{ "let_rune",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]u8 = ['A', 'B', 'C', 'D'];\n"
" return a[0]: i32;\n"
"};\n", 65 },
{ "def_int",
"package main;\n"
"def D: [4]u8 = [65, 66, 67, 68];\n"
"export fn main() i32 = { return D[0]: i32; };\n", 65 },
{ "def_rune",
"package main;\n"
"def D: [4]u8 = ['A', 'B', 'C', 'D'];\n"
"export fn main() i32 = { return D[0]: i32; };\n", 65 },
{ "struct_int",
"package main;\n"
"type enc = struct { m: [4]u8 };\n"
"let E: enc = enc { m = [65, 66, 67, 68] };\n"
"export fn main() i32 = { return E.m[0]: i32; };\n", 65 },
{ "struct_rune",
"package main;\n"
"type enc = struct { m: [4]u8 };\n"
"export fn main() i32 = {\n"
" let e: enc = enc { m = ['A', 'B', 'C', 'D'] };\n"
" return e.m[1]: i32;\n"
"};\n", 66 },
{ NULL, NULL, 0 }
};
static const struct rrow reject_rows[] = {
{ "let_over",
"package main;\n"
"export fn main() i32 = { let a: [2]u8 = [300, 1]; return a[0]: i32; };\n" },
{ "def_over",
"package main;\n"
"def D: [2]u8 = [300, 1];\n"
"export fn main() i32 = { return D[0]: i32; };\n" },
{ "struct_over",
"package main;\n"
"type enc = struct { m: [2]u8 };\n"
"let E: enc = enc { m = [300, 1] };\n"
"export fn main() i32 = { return E.m[0]: i32; };\n" },
{ "let_str",
"package main;\n"
"export fn main() i32 = { let a: [2]u8 = [\"x\", \"y\"]; return a[0]: i32; };\n" },
{ "def_str",
"package main;\n"
"def D: [2]u8 = [\"x\", \"y\"];\n"
"export fn main() i32 = { return 0; };\n" },
{ "struct_str",
"package main;\n"
"type enc = struct { m: [2]u8 };\n"
"let E: enc = enc { m = [\"x\", \"y\"] };\n"
"export fn main() i32 = { return E.m[0]: i32; };\n" },
{ NULL, NULL }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
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 w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "arrnarrow: w6c_ww missing — cannot run the "
"cs==ww gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
/* Accept rows: cstage build + run + cs==ww byte-id. */
for (int i = 0; accept_rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwan_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[160];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128], outbin[128];
snprintf(src, sizeof src, "%s/wwan_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwan_%d_%d",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(accept_rows[i].src, f);
fclose(f);
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "accept[%s]: cstage build failed\n",
accept_rows[i].label);
fail++; runwait(rmcmd); continue;
}
int got = runwait(outbin);
if (got != accept_rows[i].want_exit) {
fprintf(stderr, "accept[%s]: exit %d, want %d\n",
accept_rows[i].label, got, accept_rows[i].want_exit);
fail++;
}
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwan_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwan_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "accept[%s]: w6c failed\n", accept_rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "accept[%s]: w6c_ww failed\n", accept_rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "accept[%s]: cs/ww .s DIFFER (rule-10)\n",
accept_rows[i].label);
fail++;
}
runwait(rmcmd);
}
/* Reject rows: BOTH stages must fail to build (loud reject). */
for (int i = 0; reject_rows[i].src; i++, n++) {
char src[64];
snprintf(src, sizeof src, "/tmp/wwrn_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(reject_rows[i].src, f);
fclose(f);
char cmd[2048], dst[80];
snprintf(dst, sizeof dst, "/tmp/wwrn_%d_%d.s", getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, dst, src);
int cs_rc = runwait(cmd);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, dst, src);
int ww_rc = runwait(cmd);
if (cs_rc == 0) {
fprintf(stderr, "reject[%s]: cstage ACCEPTED (want reject)\n",
reject_rows[i].label);
fail++;
}
if (ww_rc == 0) {
fprintf(stderr, "reject[%s]: wwstage ACCEPTED (want reject)\n",
reject_rows[i].label);
fail++;
}
unlink(src); unlink(dst);
}
if (fail) {
fprintf(stderr, "%d/%d arrlit-elem-narrow tests failed\n",
fail, n);
return 1;
}
printf("arrnarrow: %d/%d ok (accept: run + cs==ww; reject: both-stage fail)\n",
n, n);
return 0;
}

View File

@@ -1,273 +0,0 @@
/*
* 953_arraytoslice_run — runtime + byte-id + reject net for #258: the
* implicit [N]T -> []T array-to-slice BORROW. Hare admits an array with a
* defined length wherever its element slice is expected (assign / return /
* call-arg / init), as a borrow — `.ptr = &arr[0], .len = N, .cap = N`
* (ref/harec/src/types.c:1080-1097, the SLICE-dst arm). ww previously
* REJECTED it everywhere (cmd/wcc/type.c:#108(c) exclusion); base64
* worked around the gap with explicit `a[0:n]` slices.
*
* Fix (DESUGAR, checker-only, ZERO new cgen): type_assignable /
* isassignable admit array→slice on an exact element match; the four
* acceptance sites (clet / N_ASSIGN / call-arg gather / N_RETURN) then
* rewrite the array expr to the explicit full slice `arr[0:len(arr)]` (an
* N_SLICE over the array base) via the shared desugar_arrayslice /
* desugararrayslice helper. cgen is untouched — the existing slice
* lowering (#252/#257/#135 made array bases, incl struct-field arrays,
* correct) materialises the borrow header, identically in both stages.
*
* Positive rows: cstage `ww build` + run for exit code (the four contexts
* + a BORROW proof — mutate through the slice, observe the change in the
* backing array, i.e. alias not copy), then w6c vs w6c_ww `.s` cmp for the
* rule-10 byte-id gate (all four contexts emit byte-identical asm).
* - let_i32 let-init borrow `let s: []i32 = a`, s[2] → 33
* - let_u8 let-init borrow, u8 element, s[3] → 66
* - assign_i32 assignment borrow `s = a` (s was a[0:1]), s[3] → 4
* - callarg_i32 call-arg borrow `sum(a)` (sums all elements) → 65
* - callarg_u8 call-arg borrow, u8 element → 70
* - return_i32 return borrow `return g` (g a global array) → 17
* - borrow_i32 mutate s[2]=55 through the slice, read a[2] → 55
*
* Reject rows (cs==ww symmetric loud-reject): an element-type MISMATCH
* ([4]i32 -> []u8) is NOT a borrow — both stages must refuse it. w6c and
* w6c_ww are each run directly and must exit non-zero (the desugar's
* element-exact gate; type_assignable / isassignable fall through to a
* confident reject).
* - mismatch_let let s: []u8 = a where a:[4]i32 → both reject
* - mismatch_callarg f(a) where f wants []u8, a:[4]i32 → both reject
*/
#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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
if (fa == NULL || fb == NULL) {
if (fa) fclose(fa);
if (fb) fclose(fb);
return -1;
}
int ca, cb, eq = 0;
do {
ca = fgetc(fa);
cb = fgetc(fb);
if (ca != cb) { eq = -1; break; }
} while (ca != EOF);
fclose(fa);
fclose(fb);
return eq;
}
struct row { const char *label; const char *src; int want_exit; int reject; };
static const struct row rows[] = {
{ "let_i32",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]i32 = [11, 22, 33, 44];\n"
" let s: []i32 = a;\n"
" return s[2];\n"
"};\n", 33, 0 },
{ "let_u8",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]u8 = [11u8, 22u8, 33u8, 66u8];\n"
" let s: []u8 = a;\n"
" return s[3]: i32;\n"
"};\n", 66, 0 },
{ "assign_i32",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]i32 = [1, 2, 3, 4];\n"
" let s: []i32 = a[0:1];\n"
" s = a;\n"
" return s[3];\n"
"};\n", 4, 0 },
{ "callarg_i32",
"package main;\n"
"fn sum(s: []i32) i32 = {\n"
" let t: i32 = 0; let i: i32 = 0;\n"
" for (i < s.len) { t += s[i]; i += 1; };\n"
" return t;\n"
"};\n"
"export fn main() i32 = {\n"
" let a: [4]i32 = [10, 20, 30, 5];\n"
" return sum(a);\n"
"};\n", 65, 0 },
{ "callarg_u8",
"package main;\n"
"fn sum(s: []u8) i32 = {\n"
" let t: i32 = 0; let i: i32 = 0;\n"
" for (i < s.len) { t += s[i]: i32; i += 1; };\n"
" return t;\n"
"};\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 40u8];\n"
" return sum(a);\n"
"};\n", 70, 0 },
{ "return_i32",
"package main;\n"
"let g: [3]i32 = [7, 8, 9];\n"
"fn mk() []i32 = { return g; };\n"
"export fn main() i32 = {\n"
" let s: []i32 = mk();\n"
" return s[1] + s[2];\n"
"};\n", 17, 0 },
{ "borrow_i32",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]i32 = [1, 2, 3, 4];\n"
" let s: []i32 = a;\n"
" s[2] = 55;\n"
" return a[2];\n"
"};\n", 55, 0 },
/* Reject rows: element-type mismatch — both stages refuse. */
{ "mismatch_let",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]i32 = [1, 2, 3, 4];\n"
" let s: []u8 = a;\n"
" return 0;\n"
"};\n", 0, 1 },
{ "mismatch_callarg",
"package main;\n"
"fn f(s: []u8) i32 = { return 0; };\n"
"export fn main() i32 = {\n"
" let a: [4]i32 = [1, 2, 3, 4];\n"
" return f(a);\n"
"};\n", 0, 1 },
{ NULL, NULL, 0, 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[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "arraytoslice: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwa2s_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[128];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwa2s_%d_%d.ww",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
fclose(f);
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwa2s_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwa2s_%d_%d_ww.s",
tmpdir, getpid(), i);
char cmd[2048];
if (rows[i].reject) {
/* Element mismatch — both stages must refuse (non-zero). */
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c ACCEPTED a "
"type-mismatch array→slice (must reject)\n",
rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww ACCEPTED a "
"type-mismatch array→slice (must reject)\n",
rows[i].label);
fail++;
}
runwait(rmcmd);
continue;
}
/* Positive row: cstage build + run for exit code. */
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwa2s_%d_%d",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* rule-10 byte-id: w6c vs w6c_ww .s. */
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d array-to-slice tests failed\n", fail, n);
return 1;
}
printf("arraytoslice: %d/%d ok (cstage run + cs==ww byte-id + reject)\n",
n, n);
return 0;
}

View File

@@ -1,43 +1,27 @@
/*
* 953_arrlit_slice_run — runtime + byte-id + reject net for #25/#31: a
* one-step array-LITERAL initialiser for a SLICE local (`let xs:[]T=[..]`).
* 953_arrlit_slice_run -- SLIMMED to the one irreducible asymmetric row.
*
* #31 (silent miscompile, cs!=ww): the #258 arrayslice borrow wrapped the
* un-addressable N_ARRLIT directly as the N_SLICE base; cgen never spilled
* the literal to a stack slot, so .ptr pointed at garbage (`let xs:[]i32=
* [10,20,30]; xs[1]` returned 1; []u8/[]str segfaulted). #25 (over-strict
* reject): a slice target fell through to the exact-element type_eq borrow
* gate, so bare-int-width ([]u8=[1,2,3]) and str elements rejected.
* The #25/#31 array-literal-slice corpus migrated to fold-2 homes (#5-C6): the
* value rows -> test/lang/arrlit_slice_test.ww (@test + T2 byte-id); the
* reject_oob / reject_call / reject_ret rows -> test/wcc/data/arrlit_slice_*
* runww //ww:error carriers (strong shared substrings, both stages).
*
* Fix (re-stamp + per-borrow scratch, both stages symmetric, NO new codegen
* shape): the checker re-stamps the slice arrlit as [count]T (per-element
* coercion + range-check, #25); cgen materialises the borrow base into a
* FRESH per-borrow @slicescr stack slot (distinct slot per borrow — a
* borrow's backing outlives the lowering, so it can't share a cached slot;
* two live borrows would otherwise alias one backing), filled via the shared
* array-init element store (#31). Supported ONLY at a `let` init — in
* call-arg / return / assign position there is no addressable backing, so
* both stages LOUD-REJECT ("bind it to a `let` first"), aligning cstage DOWN
* to wwstage per rule-10; full non-let support is deferred (#33).
* reject_assign CANNOT move to a runww //ww:error carrier: both stages REJECT an
* array-LITERAL assigned to a slice, but at DIFFERENT diagnostic LAYERS -- cstage
* at assignability ("cannot assign [3]int to []i32") and wwstage at the borrow
* gate ("array literal cannot borrow as a slice here; bind it to a `let` first").
* BOTH reject CORRECTLY (the borrow is supported only at a `let` init, #31/#33) --
* this is a benign diag-LAYER divergence, NOT a miscompile, so NO bug ticket. No
* honest shared substring exists for a #20-non-vacuity carrier, and a build-reject
* emits no .s (no byte-id surface). It stays a slim C pin asserting rc!=0 BOTH
* stages -- the position the stages diverge, most worth pinning.
*
* Accept rows (cstage `ww build` + run for exit code, then w6c vs w6c_ww .s
* cmp for the rule-10 byte-id gate):
* - i32_sum let xs:[]i32=[10,20,30]; xs[0]+xs[1]+xs[2] → 60
* - i32_idx xs[1] (the #31 pin: was 1, want 20) → 20
* - u8_coerce let zs:[]u8=[1,2,3]; bare-int→u8 width (#25) → 6
* - u8_typed let xs:[]u8=[10u8,20u8,30u8]; xs[2] → 30
* - i64_stride let xs:[]i64=[7i64,42i64]; xs[1] (8B stride) → 42
* - str_read let ys:[]str=["ab","c"]; ys[0].len*10+ys[1].len → 21
* - len_read let xs:[]i32=[10,20,30]; xs.len → 3
* - multi_live let xs=[1,2,3]; let ys=[4,5]; xs[0]+ys[0] → 5
* (SOUNDNESS PIN: fresh-per-borrow; a shared slot → 4+4=8)
* - borrow_mut xs[1]=99 through the borrow, read back → 99
*
* Reject rows (cs==ww symmetric loud-reject, both w6c and w6c_ww non-zero):
* - reject_oob let q:[]u8=[256,1] → out-of-range element (#25)
* - reject_call sum([1,2,3]) → non-let borrow (#31/#33)
* - reject_ret return [1,2,3] → non-let borrow
* - reject_assign s = [1,2,3] → non-let borrow
* Non-vacuity self-check (the pin CAN go RED): `valid_src` assigns a NAMED array
* (an addressable backing), which the #258 borrow ACCEPTS on both stages (rc==0);
* it proves the pin discriminates the array-LITERAL reject from a legitimate
* array-to-slice assign, not "all builds error". Mutation-checked: binding the
* literal to a `let` first (`let t=[1,2,3]; s=t;`) flips both stages to accept ->
* the reject leg goes RED (verified).
*/
#include <stdio.h>
#include <stdlib.h>
@@ -54,120 +38,57 @@ runwait(const char *cmd)
return -1;
}
/* reject_assign: an array LITERAL in assign position has no addressable backing
* -- both stages reject (cstage assignability, wwstage borrow gate). */
static const char *reject_src =
"package main;\n"
"export fn main() i32 = {\n"
" let s: []i32 = [0, 0];\n"
" s = [1, 2, 3];\n"
" return s[0];\n"
"};\n";
/* Vacuity control: a NAMED array assigned as a slice borrow (#258) -- both
* stages accept. Proves the pin distinguishes accept from reject. */
static const char *valid_src =
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]i32 = [1, 2, 3];\n"
" let s: []i32 = a[0:1];\n"
" s = a;\n"
" return s[0];\n"
"};\n";
/* build_only -- write `src`, build via `driver` (no run). Returns the build
* exit code (0 = accepted, nonzero = rejected). */
static int
slurp_eq(const char *a, const char *b)
build_only(const char *driver, const char *src, int seq)
{
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
if (fa == NULL || fb == NULL) {
if (fa) fclose(fa);
if (fb) fclose(fb);
return -1;
}
int ca, cb, eq = 0;
do {
ca = fgetc(fa);
cb = fgetc(fb);
if (ca != cb) { eq = -1; break; }
} while (ca != EOF);
fclose(fa);
fclose(fb);
return eq;
char tmpdir[96], srcp[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/as_%d_%d", getpid(), seq);
mkdir(tmpdir, 0755);
snprintf(srcp, sizeof srcp, "%s/c.ww", tmpdir);
snprintf(outbin, sizeof outbin, "%s/c", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(srcp, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "timeout 20 %s build -o %s %s >/dev/null 2>&1",
driver, outbin, srcp);
int brc = runwait(cmd);
runwait(rmcmd);
return brc;
}
struct row { const char *label; const char *src; int want_exit; int reject; };
static const struct row rows[] = {
{ "i32_sum",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i32 = [10, 20, 30];\n"
" return xs[0] + xs[1] + xs[2];\n"
"};\n", 60, 0 },
{ "i32_idx",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i32 = [10, 20, 30];\n"
" return xs[1];\n"
"};\n", 20, 0 },
{ "u8_coerce",
"package main;\n"
"export fn main() i32 = {\n"
" let zs: []u8 = [1, 2, 3];\n"
" return zs[0]: i32 + zs[1]: i32 + zs[2]: i32;\n"
"};\n", 6, 0 },
{ "u8_typed",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []u8 = [10u8, 20u8, 30u8];\n"
" return xs[2]: i32;\n"
"};\n", 30, 0 },
{ "i64_stride",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i64 = [7i64, 42i64];\n"
" return xs[1]: i32;\n"
"};\n", 42, 0 },
{ "str_read",
"package main;\n"
"export fn main() i32 = {\n"
" let ys: []str = [\"ab\", \"c\"];\n"
" return ys[0].len: i32 * 10 + ys[1].len: i32;\n"
"};\n", 21, 0 },
{ "len_read",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i32 = [10, 20, 30];\n"
" return xs.len: i32;\n"
"};\n", 3, 0 },
{ "multi_live",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i32 = [1, 2, 3];\n"
" let ys: []i32 = [4, 5];\n"
" return xs[0] + ys[0];\n"
"};\n", 5, 0 },
{ "borrow_mut",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: []i32 = [1, 2, 3];\n"
" xs[1] = 99;\n"
" return xs[1];\n"
"};\n", 99, 0 },
/* Reject rows: out-of-range element + the three non-let contexts. */
{ "reject_oob",
"package main;\n"
"export fn main() i32 = {\n"
" let q: []u8 = [256, 1];\n"
" return q[0]: i32;\n"
"};\n", 0, 1 },
{ "reject_call",
"package main;\n"
"fn sum(s: []i32) i32 = { return s[0]; };\n"
"export fn main() i32 = {\n"
" return sum([1, 2, 3]);\n"
"};\n", 0, 1 },
{ "reject_ret",
"package main;\n"
"fn mk() []i32 = { return [1, 2, 3]; };\n"
"export fn main() i32 = {\n"
" return mk()[0];\n"
"};\n", 0, 1 },
{ "reject_assign",
"package main;\n"
"export fn main() i32 = {\n"
" let s: []i32 = [0, 0];\n"
" s = [1, 2, 3];\n"
" return s[0];\n"
"};\n", 0, 1 },
{ NULL, NULL, 0, 0 },
};
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
char absbin[2080];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
@@ -175,108 +96,37 @@ main(void)
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "arrlit_slice: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
char cdrv[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int wwpresent = (access(wdrv, X_OK) == 0);
int fail = 0, seq = 0;
/* reject leg: both stages must REJECT (rc!=0). */
if (build_only(cdrv, reject_src, seq++) == 0) {
fprintf(stderr, "arrlit_slice[cs]: reject_assign ACCEPTED (want reject)\n");
fail++;
}
if (wwpresent && build_only(wdrv, reject_src, seq++) == 0) {
fprintf(stderr, "arrlit_slice[ww]: reject_assign ACCEPTED (want reject)\n");
fail++;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
/* src + outbin + cs.s + ws.s all under one tmpdir so the
* compiler's source-derived .sepwork scratch stays inside it;
* single rm -rf deferred to the end of the row. */
char tmpdir[64], rmcmd[160];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwas_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128], outbin[128];
snprintf(src, sizeof src, "%s/wwas_%d_%d.ww", tmpdir,
getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwas_%d_%d", tmpdir,
getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwas_%d_%d_cs.s", tmpdir,
getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwas_%d_%d_ww.s", tmpdir,
getpid(), i);
char cmd[2048];
if (rows[i].reject) {
/* Both stages must refuse (non-zero). */
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c ACCEPTED a row that "
"must reject\n", rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww ACCEPTED a row "
"that must reject\n", rows[i].label);
fail++;
}
runwait(rmcmd);
continue;
}
/* Positive row: cstage build + run for exit code. */
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* rule-10 byte-id: w6c vs w6c_ww .s. */
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
/* vacuity control: both stages must ACCEPT (rc==0). */
if (build_only(cdrv, valid_src, seq++) != 0) {
fprintf(stderr, "arrlit_slice[cs]: vacuity control REJECTED (pin may be vacuous)\n");
fail++;
}
if (wwpresent && build_only(wdrv, valid_src, seq++) != 0) {
fprintf(stderr, "arrlit_slice[ww]: vacuity control REJECTED (pin may be vacuous)\n");
fail++;
}
if (fail) {
fprintf(stderr, "%d/%d arrlit-slice tests failed\n", fail, n);
fprintf(stderr, "arrlit_slice: %d checks failed\n", fail);
return 1;
}
printf("arrlit_slice: %d/%d ok (cstage run + cs==ww byte-id + "
"reject)\n", n, n);
printf("arrlit_slice: reject_assign pin ok\n");
return 0;
}

View File

@@ -1,165 +0,0 @@
/*
* 953_globalslice_arg_run — cstage runtime pin for #148 (D2).
*
* A module-global slice (`const`/`let []T`) passed BY VALUE as a slice
* arg arrived with a GARBAGE header: the slice-IDENT call-arg fast path
* (cgen.c:9082) emitted unconditional `MOVQ off+{0,8,16}(BP)` where
* off=localfind(name). For a GLOBAL slice ident localfind→0 (locals are
* negative), so it read (BP)/8(BP)/16(BP) = saved-BP/RIP/caller garbage
* instead of the global's header at name(SB). The fix mirrors the
* sibling N_SLICE arm's isglobal dispatch: LEAQ name(SB) into a base
* reg, then push 16/8/0 off that base.
*
* Generalizes to any []T (fast path keys on node_isslice, not u8) — the
* []u32 row catches an elem-width assumption. The direct-read control
* row (global slice consumed in-place, never passed as an arg) already
* worked pre-fix; it locks that the new arm doesn't regress it.
*
* CSTAGE-ONLY: wwstage's checker rejects a module-level `const`/`let
* []T` global ("let: not assignable", #120/#29-kin) → the cgen path is
* UNREACHABLE on wwstage, so there is no .s to diverge and 990-997
* byte-id stay green. NO byte-id leg here; add it when #120 + the
* cgenexpr.ww twin (#125) land.
*/
#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;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* the path bytes.equal(dot,…) consumer shape: a global []u8 passed
* by value, the callee reading .len AND a byte. seen = len*1000 +
* byte[0]: dotdot = ['.','.'] → 2*1000 + 46 = 2046, truncated to a
* u8 exit code = 2046 & 0xff = 254. */
{ "u8_dotdot",
"package main;\n"
"const dotdot: []u8 = ['.', '.'];\n"
"fn seen(bs: []u8) i32 = {\n"
" let n = bs.len: i32; let f = bs[0]: i32;\n"
" return n*1000 + f;\n"
"};\n"
"export fn main() i32 = { return seen(dotdot) & 255; };\n",
254 },
/* single-element global []u8 — len 1, byte '.' = 46 → 1*100+46. */
{ "u8_dot",
"package main;\n"
"const dot: []u8 = ['.'];\n"
"fn seen(bs: []u8) i32 = {\n"
" let n = bs.len: i32; let f = bs[0]: i32;\n"
" return n*100 + f;\n"
"};\n"
"export fn main() i32 = { return seen(dot); };\n",
146 },
/* []u32 global — proves the fast path is element-width-agnostic
* (header is 3 words regardless of esz). len 3, g[0]=7, g[2]=9
* → 3*100 + 7 + 9 = 316 & 0xff = 60. */
{ "u32_global",
"package main;\n"
"const g: []u32 = [7u32, 8u32, 9u32];\n"
"fn seen(xs: []u32) i32 = {\n"
" let n = xs.len: i32;\n"
" return n*100 + xs[0]: i32 + xs[2]: i32;\n"
"};\n"
"export fn main() i32 = { return seen(g) & 255; };\n",
60 },
/* DIRECT-READ control — global slice consumed in place, NOT passed
* as an arg. Already correct pre-fix; locks no regression. len 2,
* d[0]=11, d[1]=22 → 2*100 + 11 + 22 = 233. */
{ "direct_read",
"package main;\n"
"const d: []u32 = [11u32, 22u32];\n"
"export fn main() i32 = {\n"
" return d.len: i32 * 100 + d[0]: i32 + d[1]: i32;\n"
"};\n",
233 },
/* LOCAL slice by-value arg control — exercises the off!=0 arm of the
* SAME fast path (cgen.c:9082), locking that the new global branch
* doesn't regress the unchanged BP-relative local push. len 2,
* byte '.' = 46 → 2*1000 + 46 = 2046 & 0xff = 254. */
{ "u8_local_arg",
"package main;\n"
"fn seen(bs: []u8) i32 = {\n"
" let n = bs.len: i32; let f = bs[0]: i32;\n"
" return n*1000 + f;\n"
"};\n"
"export fn main() i32 = {\n"
" let a: [2]u8 = ['.', '.'];\n"
" let s: []u8 = a[0:2];\n"
" return seen(s) & 255;\n"
"};\n",
254 },
{ NULL, NULL, 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;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwgsa_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char src[128], outbin[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwgsa_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwgsa_%d_%d",
tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d globalslice-arg tests failed\n",
fail, n);
return 1;
}
printf("globalslice_arg: %d/%d ok (cstage run)\n", n, n);
return 0;
}

View File

@@ -0,0 +1,9 @@
//ww:error "not assignable"
// #258 carrier: element-type mismatch at a call-arg borrow; both stages refuse.
// From test/wcc/953_arraytoslice_run.c mismatch_callarg.
package main;
fn f(s: []u8) i32 = { return 0; };
export fn main() i32 = {
let a: [4]i32 = [1, 2, 3, 4];
return f(a);
};

View File

@@ -0,0 +1,9 @@
//ww:error "not assignable"
// #258 carrier: an element-type mismatch ([4]i32 -> []u8) is NOT a borrow;
// both stages must refuse it. From test/wcc/953_arraytoslice_run.c mismatch_let.
package main;
export fn main() i32 = {
let a: [4]i32 = [1, 2, 3, 4];
let s: []u8 = a;
return 0;
};

View File

@@ -0,0 +1,9 @@
//ww:error "not assignable"
// #31/#33 carrier: an array-lit slice borrow in CALL-ARG position must REJECT
// (supported only at a `let` init). From test/wcc/953_arrlit_slice_run.c
// reject_call.
package main;
fn sum(s: []i32) i32 = { return s[0]; };
export fn main() i32 = {
return sum([1, 2, 3]);
};

View File

@@ -0,0 +1,8 @@
//ww:error "array element out of range"
// #25 carrier: an out-of-range element in a slice array-lit init must REJECT on
// both stages. From test/wcc/953_arrlit_slice_run.c reject_oob.
package main;
export fn main() i32 = {
let q: []u8 = [256, 1];
return q[0]: i32;
};

View File

@@ -0,0 +1,8 @@
//ww:error "not assignable"
// #31/#33 carrier: an array-lit slice borrow in RETURN position must REJECT.
// From test/wcc/953_arrlit_slice_run.c reject_ret.
package main;
fn mk() []i32 = { return [1, 2, 3]; };
export fn main() i32 = {
return mk()[0];
};

View File

@@ -0,0 +1,6 @@
//ww:error "array element out of range"
// #251 carrier: out-of-range element at a def (was a ww over-accept). From
// test/wcc/951_arrlit_elem_narrow_run.c def_over.
package main;
def D: [2]u8 = [300, 1];
export fn main() i32 = { return D[0]: i32; };

View File

@@ -0,0 +1,6 @@
//ww:error "not assignable"
// #251 carrier: str element at a def (was a ww over-accept). From
// test/wcc/951_arrlit_elem_narrow_run.c def_str.
package main;
def D: [2]u8 = ["x", "y"];
export fn main() i32 = { return 0; };

View File

@@ -0,0 +1,5 @@
//ww:error "array element out of range"
// #251 carrier: out-of-range int array-lit element at a local let must REJECT
// on both stages. Migrated from test/wcc/951_arrlit_elem_narrow_run.c let_over.
package main;
export fn main() i32 = { let a: [2]u8 = [300, 1]; return a[0]: i32; };

View File

@@ -0,0 +1,5 @@
//ww:error "not assignable"
// #251 carrier: str array-lit element to a [N]u8 let must REJECT (non-foldable
// element-type mismatch). From test/wcc/951_arrlit_elem_narrow_run.c let_str.
package main;
export fn main() i32 = { let a: [2]u8 = ["x", "y"]; return a[0]: i32; };

View File

@@ -0,0 +1,7 @@
//ww:error "array element out of range"
// #251 carrier: out-of-range element at a struct-field init (was a ww
// over-accept). From test/wcc/951_arrlit_elem_narrow_run.c struct_over.
package main;
type enc = struct { m: [2]u8 };
let E: enc = enc { m = [300, 1] };
export fn main() i32 = { return E.m[0]: i32; };

View File

@@ -0,0 +1,7 @@
//ww:error "not assignable"
// #251 carrier: str element at a struct-field init (was a ww over-accept).
// From test/wcc/951_arrlit_elem_narrow_run.c struct_str.
package main;
type enc = struct { m: [2]u8 };
let E: enc = enc { m = ["x", "y"] };
export fn main() i32 = { return E.m[0]: i32; };

View File

@@ -0,0 +1,12 @@
//ww:error "for-range over a non-ident array base unwired (#70)"
// #70 carrier: for-range over a non-ident ARRAY base (struct field) stays
// LOUD on both stages until a consumer wires it. Migrated from
// test/wcc/937_forrange_fieldbase_run.c reject_arr_field row.
package main;
type holder = struct { arr: [3]i64, n: i64 };
export fn main() i32 = {
let h: holder = holder { arr = [1, 2, 3], n = 0 };
let s: i64 = 0;
for (let v .. h.arr) { s += v; };
return (s: i32);
};

View File

@@ -0,0 +1,13 @@
//ww:error "for-range over a deref base unwired (#11)"
// #70 carrier: for-range over a *p deref base stays LOUD on both stages
// (the #11 deref-spine family — cgexpr does not deliver the slice header).
// Migrated from test/wcc/937_forrange_fieldbase_run.c reject_deref row.
package main;
export fn main() i32 = {
let xs: []i64;
append(xs, 1);
let q: *[]i64 = &xs;
let s: i64 = 0;
for (let v .. *q) { s += v; };
return (s: i32);
};

View File

@@ -0,0 +1,14 @@
//ww:run-exit 1
// #17 (F4) carrier: `e as T` on a null (*int|void) operand must ABORT (exit 1),
// not silently pass. Hare guarantees a failed type-assert calls rt::abort (drew)
// — not a row in @test (an abort kills `ww test`). The clean twin asrt_ptr_ok in
// test/lang/nullable_assert_test.ww carries the abort-arm byte-id (ken: ww emits
// the check-and-abort arm INLINE, so a wwstage regression diverges that twin);
// the non-redundant dimension here is the RUNTIME abort. Underlying fix was
// wwstage cgen (cgtypeassert nullable arm). From 949_nullable_assert_run.c.
package main;
export fn main() i32 = {
let p: (*int|void) = void;
let q = p as *int;
return 50;
};

View File

@@ -0,0 +1,12 @@
//ww:run-exit 1
// #17 (F4) carrier: a valid ptr `as void` must ABORT (exit 1) — the void
// polarity of the type-assert. Clean twin asrt_void_target_ok carries the
// abort-arm byte-id; this pins the runtime abort fires. From
// 949_nullable_assert_run.c (drew: rt::abort is a real Hare guarantee).
package main;
export fn main() i32 = {
let x: int = 5;
let p: (*int|void) = &x;
let z = p as void;
return 7;
};

View File

@@ -0,0 +1,12 @@
//ww:run-exit 1
// #15 (F4) carrier: `p!` on a null (*int|void) must ABORT (exit 1), not treat
// null as success and deref (SEGV pre-fix). Clean twin tryunw_valid in
// test/lang/nullable_try_test.ww carries the abort-arm byte-id; this pins the
// runtime abort. Underlying fix was wwstage cgen (cgtryunw nullable arm). From
// 949_nullable_try_run.c.
package main;
fn h(p: (*int|void)) int = { let q = p!; return *q; };
export fn main() i32 = {
let r = h(void);
return 50;
};