test: migrate str/slice global family-1 batch to test/lang @test, retire C twins (fold-2)

Continue fold-2 (after 374e97b): migrate the remaining Family-1 str/slice
global + literal + index + call-arg group from bespoke build+run C twins to
test/lang @test, retiring each twin in the same commit. Runtime coverage MOVES
from $(TESTS) to test-lang (T1 runs+asserts via `ww test`) + test-lang-byteid
(T2 keeps cs==ww .s byte-id); coverage is preserved, the $(TESTS) headline
drops 9 (434->425). All asserts are primitive int/u8/bool comparisons (no
fmt/strconv in the assert path); the slice-store/index rows reset the global
each fn and sum ADJACENT elements so a dropped/mis-strided/over-wide word FAILS.

  989_globslicefield_run.c    -> glob_slice_field_test.ww     (slice field of a global struct: g.f=<slice> stores full 24B header; len/cap/non-zero-offset + str/scalar controls)
  989_globstrslice_run.c      -> glob_str_slice_arg_test.ww   (global str sliced with default hi passed as call arg loads its len word; explicit-hi control)
  989_trystr_run.c            -> try_str_unwrap_test.ww       (`!` unwrap of str-success tagged union shuffles the str header for ident-source/error-first/success-first)
  797_len_strglobal_run.c     -> len_str_global_test.ww       (len(str-global) loads .len via name(SB); local-str control)
  801_litstr_pseudo_run.c     -> lit_str_pseudo_test.ww       (string-literal .len/.ptr pseudo-field; empty/multibyte + arg-passthrough)
  803_globalidx_run.c         -> global_index_test.ww         (global str/slice index read/addr-of/store/compound, esz 1/4; local regression pins)
  903_tuple_elem_slice_len.c  -> tuple_elem_slice_len_test.ww (len(t.N) of a slice/str tuple element loads .len at +8; 2/3-slice, str-slice both orders)
  927_composite_call_arg_run.c-> composite_call_arg_test.ww   (slice-returning CALL passed inline as a composite arg; canonical/letslice/two-call/middle/nested/scalar/tagged)
  952_slicecopy_assign_run.c  -> slice_copy_assign_test.ww    (bulk slice-copy-assign `arr[lo:hi]=bs`, esz 1/4, field/via-ptr/local bases; reslice-read companion)

rd_reslice asserts the TRUE value 360 (the .c twin's want=104 was 360 & 0xFF,
an exit-code truncation). 723_composite_call_arg.c's comment repointed to the
new test/lang location. 802_lenidx_run.c is DEFERRED (it carries //ww:error
reject rows — needs a value-rows-only split + a slim reject carrier, a fold-3
pass). Bump LANGBYTEID_EXPECTED_MIN 22->31 to ratchet the new corpus floor.
This commit is contained in:
2026-06-22 09:30:28 +09:00
parent 374e97b9e8
commit 438efab8c6
20 changed files with 623 additions and 2180 deletions

View File

@@ -13,7 +13,7 @@
* (rule 10): add an N_CALL arm to `nodeisslice` that mirrors the
* existing N_CALL arm in `nodeisstr` (cgenutil.ww:574+).
*
* 927_composite_call_arg_run pins the runtime behaviour; this row
* test/lang/composite_call_arg_test.ww pins the runtime behaviour; this row
* pins the asm shape so a future cgen refactor that re-routes
* pushargsrev can't silently regress back to the dropped-len/cap
* sequence.

View File

@@ -1,179 +0,0 @@
/*
* 797_len_strglobal_run — project #231. Runtime + cs==ww byte-id net for
* the `len(str-global)` miscompile.
*
* THE BUG (both stages wrong, DIFFERENTLY → byte-id was BLIND because the
* stages also DIVERGED, but no bootstrap source exercised the shape):
* cstage: a top-level str global N_IDENT fell into the len() arm's
* BP-relative slot load. localfind returns 0 for a global, so it
* emitted `MOVQ 8(BP),AX` — a bogus stack slot (runtime 72).
* wwstage: the len() arm only handled locals (localfindnode != nil);
* a global fell through to cgexpr, which loads the whole header
* and leaves AX=.ptr, not .len (runtime garbage / 37).
* The local-str case (`let g: str = "hello"; len(g)`) is correct in both
* (control), so the fix targets only the global path.
*
* THE FIX (#231): both stages emit the global .len load — LEAQ name(SB),CX;
* MOVQ 8(CX),AX (.len field; str/slice header is ptr@0/len@8/cap@16). The
* LEAQ symbol routes through the post-#1 value mangle (cstage mahint with
* c->cur_mod, wwstage emitsymnamehint with c.curmod), NOT a raw name —
* else a private same-module same-leaf str global would re-open the #1
* collision.
*
* SCOPE NOTE: a []u8 (slice) global would exercise the same arm (the gate
* covers TY_SLICE || TY_STR), but `let g: []u8 = [...]` literal-init is
* currently REJECTED by cstage ("let g init not assignable", #233), so the
* cstage `ww build` + byte-id rows below cannot compile a slice global yet.
* Rowed once #233 lands; the str global proves the code path now.
*
* EACH ROW CARRIES BOTH DIMENSIONS (795/796 model):
* (a) cstage `ww build` + run, asserting the exit — pins the converged
* asm is runtime-correct (len("hello") == 5).
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (they did,
* differently, pre-fix).
*
* GATE POLARITY: must stay GREEN. A wrong exit means the global .len load
* regressed; a byte-id FAIL means the stages diverged (rule-10).
*/
#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_exit; };
static const struct row rows[] = {
/* str global: len("hello") == 5. Pre-fix cstage 72, wwstage garbage. */
{ "str_global_len",
"package main;\n"
"let g: str = \"hello\";\n"
"export fn main() i32 = { return len(g): i32; };\n", 5 },
/* control: the local-str case both stages already get right. */
{ "str_local_len",
"package main;\n"
"export fn main() i32 = { let g: str = \"hello\"; "
"return len(g): i32; };\n", 5 },
{ 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, "len_strglobal: w6c_ww missing — cannot run the "
"cs==ww byte-id gate\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char src[64];
snprintf(src, sizeof src, "/tmp/wwlsg_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run in a scratch dir. */
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwlsg_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
char cmd[2048];
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
unlink(src); rmdir(tmpdir);
continue;
}
char outbin[128];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
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++;
}
unlink(outbin); rmdir(tmpdir);
/* (b) cs==ww byte-id gate. */
char cs_s[64], ws_s[64];
snprintf(cs_s, sizeof cs_s, "/tmp/wwlsg_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/wwlsg_%d_%d_ww.s", 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++; unlink(src); 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++; unlink(src); unlink(cs_s); 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++;
}
unlink(src); unlink(cs_s); unlink(ws_s);
}
if (fail) {
fprintf(stderr, "%d/%d len str-global tests failed\n", fail, n);
return 1;
}
printf("len_strglobal: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,185 +0,0 @@
/*
* 801_litstr_pseudo_run — BUG #14. Runtime + cs==ww byte-id net for the
* string-LITERAL `.len` / `.ptr` pseudo-field.
*
* THE BUG (cstage wrong, wwstage correct — rule-10 divergence):
* `"hi".len` returned the POINTER, not the length. A string literal is
* TY_UNTYPED_STR (not TY_STR), so it missed the typed slice/str
* pseudo-field gate in cgen.c's N_DOT and fell to the final fallback
* `cgexpr(lhs)`, which leaves AX=.ptr — `.len` then returned AX (the
* ptr) instead of shuffling BX (the len) into AX. wwstage's cgdot
* catch-all (cgenexpr.ww) already did the BX->AX shuffle, so the two
* stages DISAGREED. byte-id was BLIND: no bootstrap source uses
* literal `.len` (devs hardcoded the lengths around it, e.g.
* `os.write(2, "...".ptr, 9u64)`), so the gate never exercised it.
* The str-VARIABLE `.len` path was correct on both (control).
*
* THE FIX (#14): align cstage UP to wwstage — the final N_DOT fallback
* emits `MOVQ BX, AX` for `.len` (cgen.c). `.ptr` already returned AX
* (correct) and is unchanged.
*
* EACH ROW CARRIES BOTH DIMENSIONS (795/796/797 model):
* (a) cstage `ww build` + run, asserting the exit — pins the converged
* asm is runtime-correct (literal lengths reach the program).
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (they did,
* pre-fix: cstage emitted the ptr, wwstage the len).
*
* GATE POLARITY: must stay GREEN. A wrong exit means literal `.len`/`.ptr`
* regressed; a byte-id FAIL means the stages diverged (rule-10).
*/
#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_exit; };
static const struct row rows[] = {
/* literal .len: "hello".len == 5. Pre-fix cstage returned the ptr. */
{ "lit_len",
"package main;\n"
"export fn main() i32 = { return \"hello\".len: i32; };\n", 5 },
/* empty + multi-byte: ""/"héllo\\n" — é is 2 UTF-8 bytes, so the
* literal is 7 bytes (h,é=2,l,l,o,\\n). Guards against a .ptr/.len
* length confusion (a ptr would not be 7). */
{ "lit_len_empty_multibyte",
"package main;\n"
"export fn main() i32 = { if (\"\".len != 0) { return 9; }; "
"return \"héllo\\n\".len: i32; };\n", 7 },
/* literal .ptr still points at the bytes (deref first byte == 'h'),
* then .len == 5 — proves the fix left .ptr untouched. */
{ "lit_ptr_deref",
"package main;\n"
"export fn main() i32 = { let p: *u8 = \"hello\".ptr; "
"if (*p != 'h') { return 1; }; return \"hello\".len: i32; };\n", 5 },
/* callee-side .len on a literal passed as a str arg — the real-world
* shape (os.write(1, lit.ptr, lit.len)) that surfaced #14. */
{ "lit_arg_passthrough",
"package main;\n"
"fn slen(s: str) i32 = { return s.len: i32; };\n"
"export fn main() i32 = { if (slen(\"abcdef\") != 6) { return 1; }; "
"return \"abcdef\".len: i32; };\n", 6 },
{ 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, "litstr_pseudo: w6c_ww missing — cannot run the "
"cs==ww byte-id gate\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char src[64];
snprintf(src, sizeof src, "/tmp/wwlsp_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run in a scratch dir. */
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwlsp_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
char cmd[2048];
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
unlink(src); rmdir(tmpdir);
continue;
}
char outbin[128];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
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++;
}
unlink(outbin); rmdir(tmpdir);
/* (b) cs==ww byte-id gate. */
char cs_s[64], ws_s[64];
snprintf(cs_s, sizeof cs_s, "/tmp/wwlsp_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/wwlsp_%d_%d_ww.s", 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++; unlink(src); 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++; unlink(src); unlink(cs_s); 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++;
}
unlink(src); unlink(cs_s); unlink(ws_s);
}
if (fail) {
fprintf(stderr, "%d/%d litstr pseudo-field tests failed\n", fail, n);
return 1;
}
printf("litstr_pseudo: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,385 +0,0 @@
/*
* 803_globalidx_run — BUG #10 + #11. Runtime + cs==ww byte-id net for
* the GLOBAL `str` / GLOBAL slice index family (`s/g` are module-level lets):
* #10 — the READ `s[i]` / `g[i]` (cgindex).
* #11 — the ADDR-OF `&s[i]` / `&g[i]` (cgun) and the STORE `g[i] = v`
* (cgassign). Same root as #10 (the N_TARRAY/N_TPTR kind whitelist
* in the global-resolution arm), three more sibling arms; the store
* miscompile is an 8-byte OUT-OF-BOUNDS write (full-word MOVQ instead
* of MOVB), so the store rows below also assert ADJACENT elements stay
* uncorrupted.
*
* THE BUG (wwstage WRONG, cstage correct — a rule-10 divergence):
* wwstage `cgindex` (selfhost/cmd/wcc/cgenexpr.ww) dispatched the element
* size + base-materialisation off the base's tnode KIND, enumerating only
* N_TARRAY (global `[N]T`) and N_TPTR (global `*T`). A global str (tnode
* N_TNAME "str") and a global slice (N_TSLICE) matched NEITHER arm, so esz
* stayed at the default 8 and the base fell to the wide-header fallback —
* it materialised the full {ptr,len,cap} header and indexed with an 8-byte
* stride + a full-word MOVQ load. So `s[1]` over a global str read 8 bytes
* at ptr+8 instead of the single byte at ptr+1 (cstage emits MOVZBQ).
* cstage `case N_INDEX:` (cmd/w6c/cgen.c) dispatches esz off the RESOLVED
* base TYPE (`idx_eff(lhs->type)->sub->size`), uniform across local/global/
* str/slice/ptr — so it was already correct. LOCAL str/slice index was also
* already byte-id-clean (esz resolves off the local's tnode).
*
* THE FIX (#10): align cgindex's global-resolution arm UP to cstage's uniform
* type-driven dispatch — the same template the sister fn `cgslice` already
* uses (generic globaltn, esz = elemsizeofc(c, tn), base = N_TARRAY ? LEAQ :
* MOVQ name(SB)). A global str/slice now resolves esz=1 off the type table
* and routes through the existing isglobalptr emission (MOVQ name(SB),BX;
* ADDQ; MOVZBQ (BX),AX) — byte-identical to cstage.
*
* EACH ROW CARRIES BOTH DIMENSIONS (802 model):
* (a) cstage `ww build` + run, asserting the exit — pins that the converged
* asm reads the correct element value, not a ptr word.
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10). A
* stride-8 regression of the fix re-diverges wwstage from cstage here.
*
* BYTE-ID SCOPE PER ROW:
* Most rows compare the FULL `.s`. The global-slice row compares the TEXT
* section only (lines before the first DATA/GLOBL directive): a bare
* module-level `let g: []u8;` decl emits a divergent zero-header DATAW in
* wwstage that cstage omits — a SEPARATE, pre-existing data-emission gap
* (module-level slice static-init, task #7/#18 family) orthogonal to the
* index read. The TEXT comparison still pins the index codegen exactly
* (where a stride-8 regression manifests: MOVZBQ vs IMULQ $8 + MOVQ), so
* the #10 fix is fully gated; only the unrelated DATA noise is excluded.
* (Same kind of orthogonal block 802 documents for its slice rows.)
*
* GATE POLARITY: must stay GREEN. A wrong exit means a global str/slice index
* regressed back to a ptr-word read; a byte-id FAIL means the stages diverged.
*/
#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_exit; int text_only; };
static const struct row rows[] = {
/* the exact #10 repro: a global str "ABC", s[1] == 'B' == 66.
* Pre-fix this read an 8-byte word at ptr+8 (wide-header stride). */
{ "gstr_mid",
"package main;\n"
"let s: str = \"ABC\";\n"
"export fn main() i32 = { return s[1]: i32; };\n", 66, 0 },
/* first element s[0] == 'A' == 65 — guards an off-by-one in the
* index scale (a wide stride would land elsewhere). */
{ "gstr_first",
"package main;\n"
"let s: str = \"ABC\";\n"
"export fn main() i32 = { return s[0]: i32; };\n", 65, 0 },
/* last element s[2] == 'C' == 67. */
{ "gstr_last",
"package main;\n"
"let s: str = \"ABC\";\n"
"export fn main() i32 = { return s[2]: i32; };\n", 67, 0 },
/* two global-str indices summed (65 + 66 == 131) — pins the byte
* load width: a full-word load would carry the high bytes. */
{ "gstr_sum",
"package main;\n"
"let s: str = \"ABC\";\n"
"export fn main() i32 = { return s[0]: i32 + s[1]: i32; };\n", 131, 0 },
/* global slice index: g[1] == 20. The bare `let g: []u8;` decl emits
* a divergent zero-header DATAW (orthogonal #7/#18 data gap), so this
* row is TEXT-only byte-id; cstage runtime pins the value, TEXT-id
* pins the index read. g is assigned at runtime to dodge the SEPARATE
* module-level slice-literal static-init gap (which fails to link in
* BOTH stages identically). */
{ "gslice_mid",
"package main;\n"
"let g: []u8;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" g = a;\n"
" return g[1]: i32;\n"
"};\n", 20, 1 },
/* global slice with a WIDTH>1, SIGNED element: g[1] - g[0] == 20 - (-5)
* == 25. Pins that the global path resolves esz=4 off the type table
* (not the default 8) AND sign-extends (MOVSXD) — a stride-8 regression
* re-diverges the TEXT (IMULQ $8 + MOVQ word vs IMULQ $4 + MOVSXD). Same
* bare-decl DATAW data-gap as gslice_mid, so TEXT-only. */
{ "gislice_mid",
"package main;\n"
"let g: []i32;\n"
"export fn main() i32 = {\n"
" let a: [3]i32 = [-5, 20, 30];\n"
" g = a;\n"
" return g[1] - g[0];\n"
"};\n", 25, 1 },
/* #11 ADDR-OF, global str: &s[1] then read the byte through the
* pointer == 'B' == 66. Pre-#11 cgun materialised the wide {ptr,len,
* cap} header + an 8-byte stride (IMULQ $8 + LEAQ s(SB)) instead of
* MOVQ s(SB) (.ptr) + ADDQ; FULL byte-id (string-literal DATA is
* stage-identical, same as the gstr READ rows). */
{ "gstr_addr",
"package main;\n"
"let s: str = \"ABC\";\n"
"export fn main() i32 = { let p: *u8 = &s[1]; return (*p): i32; };\n",
66, 0 },
/* #11 ADDR-OF, global slice []u8: &g[1] read back == 20. Bare
* `let g: []u8;` decl → orthogonal zero-header DATAW, so TEXT-only
* (same data gap as gslice_mid; the addr codegen is all TEXT). */
{ "gslice_addr",
"package main;\n"
"let g: []u8;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" g = a;\n"
" let p: *u8 = &g[1];\n"
" return (*p): i32;\n"
"};\n", 20, 1 },
/* #11 ADDR-OF, global slice WIDTH>1: &g[1] over []i32, *p == 20.
* Pins esz=4 stride on the addr path — a stride-8 regression lands
* &g[2] (*p == 30) and re-diverges the TEXT (IMULQ $4 vs IMULQ $8).
* Same bare-decl DATAW gap → TEXT-only. */
{ "gislice_addr",
"package main;\n"
"let g: []i32;\n"
"export fn main() i32 = {\n"
" let a: [3]i32 = [-5, 20, 30];\n"
" g = a;\n"
" let p: *i32 = &g[1];\n"
" return *p;\n"
"};\n", 20, 1 },
/* #11 STORE, global slice []u8: g[1] = 99, then g[1]+g[0]+g[2] ==
* 99 + 10 + 30 == 139. The g[0]/g[2] addends are the OOB-WRITE GUARD:
* pre-#11 the store was a full-word MOVQ at an 8-byte stride, writing
* 99 at ptr+8 (8-byte OOB) and leaving g[1] == 20 (sum 60, AND clobbered
* memory). The fix stores MOVB at ptr+1; adjacent bytes intact. TEXT-
* only (bare-decl DATAW gap). */
{ "gslice_store",
"package main;\n"
"let g: []u8;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" g = a;\n"
" g[1] = 99u8;\n"
" return g[1]: i32 + g[0]: i32 + g[2]: i32;\n"
"};\n", 139, 1 },
/* #11 STORE, global slice WIDTH>1 SIGNED: g[1] = 42, then
* g[1]+g[0]+g[2] == 42 + (-5) + 30 == 67. Pins esz=4 store WIDTH +
* stride: a full-word/stride-8 regression writes 8 bytes at ptr+8
* (OOB) and re-diverges the TEXT (IMULQ $4 + MOVL vs IMULQ $8 + MOVQ).
* g[0]/g[2] are the adjacency guard. TEXT-only. */
{ "gislice_store",
"package main;\n"
"let g: []i32;\n"
"export fn main() i32 = {\n"
" let a: [3]i32 = [-5, 20, 30];\n"
" g = a;\n"
" g[1] = 42;\n"
" return g[1] + g[0] + g[2];\n"
"};\n", 67, 1 },
/* #11 COMPOUND STORE, global slice []u8: g[1] += 79 → 20+79 == 99,
* then g[1]+g[0]+g[2] == 99+10+30 == 139. Exercises the THIRD fixed
* arm (cgenexpr.ww cgassign n.op != TK_ASSIGN), a distinct code path
* from the plain-store rows above: it load-combines-stores in place.
* Pre-#11 it hit the same kind whitelist → esz=8 + a full-word RMW: an
* 8-byte OOB load AND store at ptr+8, leaving g[1] == 20 (sum 60) and
* clobbering adjacent memory. The g[0]/g[2] addends are the OOB guard.
* TEXT-only (bare-decl DATAW gap). */
{ "gslice_compound",
"package main;\n"
"let g: []u8;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" g = a;\n"
" g[1] += 79u8;\n"
" return g[1]: i32 + g[0]: i32 + g[2]: i32;\n"
"};\n", 139, 1 },
/* #11 COMPOUND STORE, global slice WIDTH>1 SIGNED: g[1] += 47 →
* (-5)+47 == 42, then g[1]+g[0]+g[2] == 42+(-25)+30 == 47. Pins the
* compound arm's esz=4 load/store WIDTH + stride on the global path —
* a stride-8/full-word RMW regression reads+writes 8 bytes at ptr+8
* (OOB) and re-diverges the TEXT (IMULQ $4 + MOVL/MOVSXD vs IMULQ $8 +
* MOVQ). g[0]/g[2] are the adjacency guard. TEXT-only. */
{ "gislice_compound",
"package main;\n"
"let g: []i32;\n"
"export fn main() i32 = {\n"
" let a: [3]i32 = [-25, -5, 30];\n"
" g = a;\n"
" g[1] += 47;\n"
" return g[1] + g[0] + g[2];\n"
"};\n", 47, 1 },
/* REGRESSION PIN: local str ADDR-OF (already clean pre-#11) — the
* fix must not perturb the local path. &s[1] read back == 66. FULL. */
{ "lstr_addr",
"package main;\n"
"export fn main() i32 = { let s: str = \"ABC\"; let p: *u8 = &s[1]; return (*p): i32; };\n",
66, 0 },
/* REGRESSION PIN: local slice STORE (already clean) — g[1] = 99 then
* g[1]+g[0]+g[2] == 139, adjacency intact. FULL byte-id (a local slice
* carries no bare-decl data gap). */
{ "lslice_store",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" let g: []u8 = a;\n"
" g[1] = 99u8;\n"
" return g[1]: i32 + g[0]: i32 + g[2]: i32;\n"
"};\n", 139, 0 },
/* REGRESSION PIN: local str index (already byte-id-clean pre-fix) —
* the fix must not perturb the local path. s[1] == 'B' == 66. */
{ "lstr_mid",
"package main;\n"
"export fn main() i32 = { let s: str = \"ABC\"; return s[1]: i32; };\n",
66, 0 },
/* REGRESSION PIN: local slice index (already clean). g[2] == 30. */
{ "lslice_last",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" let g: []u8 = a;\n"
" return g[2]: i32;\n"
"};\n", 30, 0 },
/* REGRESSION PIN: local array index (already clean). a[1] == 20. */
{ "larr_mid",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]u8 = [10u8, 20u8, 30u8];\n"
" return a[1]: i32;\n"
"};\n", 20, 0 },
{ NULL, NULL, 0, 0 }
};
/* A directive line that begins the DATA/GLOBL section. The TEXT-only
* compare stops at the first such line (the index codegen lives entirely
* in the TEXT segment above it). */
static int
isdataline(const char *ln)
{
return strncmp(ln, "DATA", 4) == 0 || strncmp(ln, "GLOBL", 5) == 0;
}
/* Byte-compare two .s files. With text_only, both files are truncated at
* the first DATA/GLOBL line before comparison (orthogonal data-section
* divergence excluded; see the BYTE-ID SCOPE note above). */
static int
asm_eq(const char *a, const char *b, int text_only)
{
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;
char la[4096], lb[4096];
for (;;) {
char *ra = fgets(la, sizeof la, fa);
char *rb = fgets(lb, sizeof lb, fb);
if (text_only && ra && isdataline(la)) ra = NULL;
if (text_only && rb && isdataline(lb)) rb = NULL;
if (ra == NULL && rb == NULL) break;
if (ra == NULL || rb == NULL) { rc = -1; break; }
if (strcmp(la, lb) != 0) { rc = -1; 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, "globalidx: w6c_ww missing — cannot run the "
"cs==ww byte-id gate\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char src[64];
snprintf(src, sizeof src, "/tmp/wwgi_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run in a scratch dir. */
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwgi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
char cmd[2048];
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
unlink(src); rmdir(tmpdir);
continue;
}
char outbin[128];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
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++;
}
unlink(outbin); rmdir(tmpdir);
/* (b) cs==ww byte-id gate. */
char cs_s[64], ws_s[64];
snprintf(cs_s, sizeof cs_s, "/tmp/wwgi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/wwgi_%d_%d_ww.s", 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++; unlink(src); 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++; unlink(src); unlink(cs_s); continue;
}
if (asm_eq(cs_s, ws_s, rows[i].text_only) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER%s "
"(rule-10 byte-id violation)\n", rows[i].label,
rows[i].text_only ? " (TEXT section)" : "");
fail++;
}
unlink(src); unlink(cs_s); unlink(ws_s);
}
if (fail) {
fprintf(stderr, "%d/%d globalidx tests failed\n", fail, n);
return 1;
}
printf("globalidx: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,252 +0,0 @@
/*
* 903_tuple_elem_slice_len_run — project #235: `len(t.N)` where t is a tuple
* and t.N is a slice/str-typed element.
*
* The len() builtin special-cased only a PLAIN N_IDENT slice operand (load
* .len at BP+off+8) and an array operand (fold $alen); every other shape fell
* back to a bare `cgexpr(operand)`, which for a slice leaves AX=.ptr. The
* tuple-element read (`t.N`) loads only AX=.ptr (it has no slice-header
* sibling — that gap is the separate #238), so `len(t.N)` returned the slice's
* .ptr word AS the length — a SILENT miscompile, gate-blind because the
* bootstrap never does len() on a slice-typed tuple element.
*
* Fix (both stages, byte-identical per rule 10): for a slice/str tuple-element
* len() operand, load the element's .len word directly at
* BP + tuple_off + element_off + 8 — mirroring the N_IDENT slice arm and the
* tuple-field-offset walk. The +8 is the {ptr,len,cap}-header .len offset, the
* same constant the N_IDENT arm and the .len pseudo-field use; element_off sums
* preceding element sizes through the type table.
*
* Rows (every element a DISTINCT length so a dropped/wrong field is caught;
* each ww program self-asserts and returns 0 only when every len(t.N) is
* correct). Tuples are slice/str-ONLY — a leading scalar element exercises a
* SEPARATE pre-existing mixed-tuple sret-layout cs/ww divergence (filed apart
* from #235), out of scope here.
* two_slice ([]u8,[]u8) lens 3,5
* three_slice ([]u8,[]u8,[]u8) lens 3,5,11
* str_slice (str,[]u8) len("abcd")=4, slice 6
* slice_str ([]u8,str) slice 7, len("hi")=2
*
* 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 (940/945/799 precedent).
*/
#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;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "two_slice",
"package main;\n"
"fn mk() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 3; a.cap = 7;\n"
" let b: []u8; b.len = 5; b.cap = 9;\n"
" return (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: ([]u8, []u8) = mk();\n"
" if (len(t.0) != 3) { return 1; };\n"
" if (len(t.1) != 5) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "three_slice",
"package main;\n"
"fn mk() ([]u8, []u8, []u8) = {\n"
" let a: []u8; a.len = 3; a.cap = 7;\n"
" let b: []u8; b.len = 5; b.cap = 9;\n"
" let c: []u8; c.len = 11; c.cap = 13;\n"
" return (a, b, c);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: ([]u8, []u8, []u8) = mk();\n"
" if (len(t.0) != 3) { return 1; };\n"
" if (len(t.1) != 5) { return 2; };\n"
" if (len(t.2) != 11) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
{ "str_slice",
"package main;\n"
"fn mk() (str, []u8) = {\n"
" let s: str = \"abcd\";\n"
" let b: []u8; b.len = 6; b.cap = 8;\n"
" return (s, b);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: (str, []u8) = mk();\n"
" if (len(t.0) != 4) { return 1; };\n"
" if (len(t.1) != 6) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "slice_str",
"package main;\n"
"fn mk() ([]u8, str) = {\n"
" let b: []u8; b.len = 7; b.cap = 9;\n"
" let s: str = \"hi\";\n"
" return (b, s);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: ([]u8, str) = mk();\n"
" if (len(t.0) != 7) { return 1; };\n"
" if (len(t.1) != 2) { 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[96], tmpdir[96], errf[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/telen_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/telen_%d_d_%d", getpid(), i);
snprintf(errf, sizeof errf, "/tmp/telen_%d_e_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s",
tmpdir, driver, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); unlink(errf); rmdir(tmpdir);
return -1;
}
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[256];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
unlink(src); unlink(outbin); unlink(errf); rmdir(tmpdir);
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/telen_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/telen_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/telen_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, "tuple_elem_slice_len: 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, "tuple_elem_slice_len: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tuple_elem_slice_len: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,289 +0,0 @@
/*
* 927_composite_call_arg_run — runtime sentinel for #24 (Class A
* wwstage cgen miscompile). When a 3-reg composite (`[]u8` slice)
* CALL result is passed inline as a composite arg to another call,
* pre-fix wwstage emitted a single `PUSHQ AX` (loses .len/.cap) and
* under-popped the receiver's arg-regs by two words. Net: arg-shift
* collision corrupts every subsequent arg; the receiver reads the
* caller's spilled `p.ptr` as its own `s.len`, etc.
*
* Fix-site: `nodeisslice` in selfhost/cmd/wcc/cgenutil.ww gained an
* N_CALL arm mirroring `nodeisstr`'s existing one. Both the push side
* (pushargsrev's 3-PUSH `CX, BX, AX` arm) and the pop side (cgcall's
* `extra = 2`) then fire for slice-returning CALLs as args.
*
* Rows pin the runtime semantics across the matrix rob-pike outlined:
* (a) canonical `f(g())` with g returning `[]u8`.
* (b) slice-CALL then let-slice `f(g(), p)` (strings.hasprefix shape).
* (c) two composite CALLs `f(g(in1), g(in2))` (arg-shift collision).
* (d) slice-CALL in middle arg position `f(p, g(), q)`.
* (e) slice-CALL as last arg `f(p, q, g())` (stack-spill region).
* (f) nested composite `f(g(h(s)))` (composite-in-composite).
* (g) tagged-CALL regression alongside slice-CALL — confirm #21
* still holds and the two natural-push arms compose.
* (h) slice-CALL followed by a scalar `f(g(), 42)` (pop-count mix).
*/
#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[] = {
/* (a) canonical: f(g()) with g returning `[]u8`. Pre-fix wwstage
* dispatches via fewer PUSHes; check.len reads .ptr-low (a tiny
* non-13 number) → return 11. */
{ "canonical_slice_call",
"fn view(s: str) []u8 = {\n"
" let r: []u8;\n"
" r.ptr = s.ptr;\n"
" r.len = s.len;\n"
" r.cap = s.len;\n"
" return r;\n"
"};\n"
"fn check(a: []u8) i32 = {\n"
" if (a.len == 13) { return 0; };\n"
" return 11;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: str = \"hello, world!\";\n"
" return check(view(s));\n"
"};\n",
0 },
/* (b) f(g(), p) — strings.hasprefix shape: slice-CALL into 6-reg
* argpack, let-slice as second. Pre-fix p.ptr lands in DI/SI
* instead of R8/R9; check2 sees garbage for `b.len`. */
{ "slice_call_then_letslice",
"fn view(s: str) []u8 = {\n"
" let r: []u8;\n"
" r.ptr = s.ptr;\n"
" r.len = s.len;\n"
" r.cap = s.len;\n"
" return r;\n"
"};\n"
"fn check2(a: []u8, b: []u8) i32 = {\n"
" if (a.len != 13) { return 11; };\n"
" if (b.len != 5) { return 12; };\n"
" return 0;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: str = \"hello, world!\";\n"
" let p: []u8;\n"
" p.ptr = s.ptr;\n"
" p.len = 5;\n"
" p.cap = 5;\n"
" return check2(view(s), p);\n"
"};\n",
0 },
/* (c) f(g(in1), g(in2)) — two composite CALLs back-to-back. The
* second call's pushargsrev runs first (right-to-left), so the
* stack must hold (a.cap,a.len,a.ptr,b.cap,b.len,b.ptr) before
* the 6-POP drain. Pre-fix only `a.ptr` and `b.ptr` made it. */
{ "two_composite_calls_args",
"fn view(s: str) []u8 = {\n"
" let r: []u8;\n"
" r.ptr = s.ptr;\n"
" r.len = s.len;\n"
" r.cap = s.len;\n"
" return r;\n"
"};\n"
"fn check2(a: []u8, b: []u8) i32 = {\n"
" if (a.len != 3) { return 11; };\n"
" if (b.len != 5) { return 12; };\n"
" return 0;\n"
"};\n"
"export fn main() i32 = {\n"
" let s1: str = \"foo\";\n"
" let s2: str = \"hello\";\n"
" return check2(view(s1), view(s2));\n"
"};\n",
0 },
/* (d) slice-CALL in middle of three args. Composite arg-shift
* collision pulls scalar arg2 into the wrong reg-class window. */
{ "slice_call_middle_arg",
"fn view(s: str) []u8 = {\n"
" let r: []u8;\n"
" r.ptr = s.ptr;\n"
" r.len = s.len;\n"
" r.cap = s.len;\n"
" return r;\n"
"};\n"
"fn check3(k0: i32, a: []u8, k1: i32) i32 = {\n"
" if (k0 != 7) { return 11; };\n"
" if (a.len != 4) { return 12; };\n"
" if (k1 != 9) { return 13; };\n"
" return 0;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: str = \"abcd\";\n"
" return check3(7, view(s), 9);\n"
"};\n",
0 },
/* (e) f(g(h(s))) — composite-in-composite nesting. Both inner and
* outer calls must route slice CALL → slice arg through the same
* pushargsrev path. */
{ "nested_composite_calls",
"fn view(s: str) []u8 = {\n"
" let r: []u8;\n"
" r.ptr = s.ptr;\n"
" r.len = s.len;\n"
" r.cap = s.len;\n"
" return r;\n"
"};\n"
"fn passthrough(a: []u8) []u8 = {\n"
" return a;\n"
"};\n"
"fn check(a: []u8) i32 = {\n"
" if (a.len == 6) { return 0; };\n"
" return 11;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: str = \"abcdef\";\n"
" return check(passthrough(view(s)));\n"
"};\n",
0 },
/* (f) slice-CALL followed by scalar — pop-count mix; pre-fix the
* scalar would land off-by-2 in the int-stream. */
{ "slice_call_then_scalar",
"fn view(s: str) []u8 = {\n"
" let r: []u8;\n"
" r.ptr = s.ptr;\n"
" r.len = s.len;\n"
" r.cap = s.len;\n"
" return r;\n"
"};\n"
"fn use2(a: []u8, k: i32) i32 = {\n"
" if (a.len != 13) { return 11; };\n"
" if (k != 99) { return 12; };\n"
" return 0;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: str = \"hello, world!\";\n"
" return use2(view(s), 99);\n"
"};\n",
0 },
/* (g) tagged-CALL regression alongside the slice path. Confirms
* #21's natural-push arm composes with #24's. The dispatch shape
* mirrors lib/encoding/utf8.next's call-chain in selfhost. */
{ "tagged_call_regression",
"type oserror = !i32;\n"
"fn yield_ptr() (*u8 | oserror) = {\n"
" let p: *u8 = nil;\n"
" return p;\n"
"};\n"
"fn dispatch(v: (*u8 | oserror)) i32 = {\n"
" match (v) {\n"
" case let p: *u8 => return 7;\n"
" case let e: oserror => return e: i32;\n"
" };\n"
" return -99;\n"
"};\n"
"export fn main() i32 = {\n"
" if (dispatch(yield_ptr()) != 7) { return 11; };\n"
" return 0;\n"
"};\n",
0 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/cca_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/cca_%d_d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
tmpdir, driver, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); rmdir(tmpdir);
return -1;
}
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
return got;
}
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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr,
"composite_call_arg_run: 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,
"composite_call_arg_run[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr,
"composite_call_arg_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("composite_call_arg_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,262 +0,0 @@
/*
* 952_slicecopy_assign_run — runtime + cs==ww byte-id net for #145
* (c1.5a): bulk slice-copy-assign into a range place `s.arr[lo:hi] = bs`
* (the LHS is an N_SLICE). No legacy N_ASSIGN arm caught an N_SLICE LHS,
* so the statement fell through to the scalar tail and emitted NOTHING —
* a silent no-op, BOTH stages, byte-id-green (#263-class). It blocked
* the path c2 port's appendlit (ref/hare/path/stack.ha:72
* `buf.buf[newend..newend+len(bs)] = bs`).
*
* The fix reuses the N_SLICE READ lowering (AX = base+lo*esz, BX = hi-lo,
* CX = cap), scales BX by the SAME read-path element width (esz; [N]u8 ->
* 1, [N]i32 -> 4), and emits a runtime-counted byte-granular copy loop
* from bs.ptr (the length is RUNTIME — w6a has no REP/MOVSB and the
* #265/#268 memcpy emitters are compile-time-sz unrolled). The Hare
* len(bs)==hi-lo assert is deferred to task #149 (documented, not a c2
* blocker — appendlit's lengths are equal by construction).
*
* WRITE-twin rows assert: the copy lands the right bytes in [lo,hi) AND
* leaves bytes OUTSIDE the range untouched (every row sums members both
* inside and outside the range, so a mis-strided or over-wide copy
* fails). esz coverage: [N]u8 (esz=1) and [N]i32 (esz=4). Base coverage:
* a struct-field array (N_DOT, the appendlit shape), a struct field via a
* *struct param (via_ptr), and a bare-local array (N_IDENT). READ-twin
* rows (drew's #5, already clean) lock the reslice-consume of the field
* array. byteid=1 throughout: post-fix the emission is byte-identical
* (the loop labels share the mklabel prefix/count/order across stages).
*/
#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_exit; int byteid; };
static const struct row rows[] = {
/* WRITE twin — struct-field [N]u8 base (the appendlit shape). */
{ "w_u8_lo0",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"export fn main() i32 = {\n"
" let s: buffer;\n"
" let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n"
" s.buf[0:3] = bs[0:3];\n"
" return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n"
" +s.buf[4]+s.buf[5]): i32;\n"
"};\n", 60, 1 },
{ "w_u8_lo2",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"export fn main() i32 = {\n"
" let s: buffer;\n"
" let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n"
" s.buf[2:5] = bs[0:3];\n"
" return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n"
" +s.buf[4]+s.buf[5]): i32;\n"
"};\n", 60, 1 },
{ "w_u8_single",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"export fn main() i32 = {\n"
" let s: buffer;\n"
" let bs: [1]u8; bs[0]=42u8;\n"
" s.buf[3:4] = bs[0:1];\n"
" return (s.buf[2]+s.buf[3]+s.buf[4]): i32;\n"
"};\n", 42, 1 },
/* empty range hi==lo — count=0, the loop's CMPQ $0 guard must
* JLE-exit before the first MOVB (off-by-one underflow would
* clobber buf[lo] and run away). Nothing in buf may change. */
{ "w_empty",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"export fn main() i32 = {\n"
" let s: buffer; s.buf[2]=5u8; s.buf[3]=7u8;\n"
" let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n"
" s.buf[2:2] = bs[0:0];\n"
" return (s.buf[1]+s.buf[2]+s.buf[3]+s.buf[4]): i32;\n"
"};\n", 12, 1 },
/* esz=4 — [N]i32 field strides by the element width, not 1. */
{ "w_i32",
"package main;\n"
"type box = struct { o: [8]i32 };\n"
"export fn main() i32 = {\n"
" let s: box;\n"
" let bs: [2]i32; bs[0]=40; bs[1]=88;\n"
" s.o[1:3] = bs[0:2];\n"
" return (s.o[0]+s.o[1]+s.o[2]+s.o[3]): i32;\n"
"};\n", 128, 1 },
/* via_ptr — the field write through a *struct param (appendlit's
* `buf: *buffer` shape). */
{ "w_viaptr",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"fn wr(s: *buffer, src: []u8) void = { s.buf[1:4] = src; };\n"
"export fn main() i32 = {\n"
" let s: buffer;\n"
" let bs: [3]u8; bs[0]=11u8; bs[1]=22u8; bs[2]=33u8;\n"
" wr(&s, bs[0:3]);\n"
" return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n"
" +s.buf[4]): i32;\n"
"};\n", 66, 1 },
/* N_IDENT base — a bare-local array place (slicebaseesz IDENT leg). */
{ "w_local",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [8]u8;\n"
" let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n"
" a[2:5] = bs[0:3];\n"
" return (a[0]+a[1]+a[2]+a[3]+a[4]+a[5]): i32;\n"
"};\n", 60, 1 },
/* READ twin (drew's #5, already CLEAN) — reslice the field array with
* a runtime hi bound, consume as []u8. Locks the read companion. */
{ "rd_reslice",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"export fn main() i32 = {\n"
" let s: buffer;\n"
" let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n"
" s.buf[0:3] = bs[0:3];\n"
" s.end = 3;\n"
" let v: []u8 = s.buf[0:s.end];\n"
" return (v[0]: i32 + v[1]: i32 + v[2]: i32 + (v.len: i32)*100);\n"
"};\n", 104, 1 },
{ "rd_len",
"package main;\n"
"type buffer = struct { buf: [8]u8, end: i32 };\n"
"export fn main() i32 = {\n"
" let s: buffer;\n"
" s.end = 5;\n"
" let v: []u8 = s.buf[0:s.end];\n"
" return v.len: i32;\n"
"};\n", 5, 1 },
{ NULL, NULL, 0, 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, "slicecopy: 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 src[64];
snprintf(src, sizeof src, "/tmp/wwsca_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsca_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char cmd[2048];
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
unlink(src); rmdir(tmpdir);
continue;
}
char outbin[128];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
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++;
}
unlink(outbin); rmdir(tmpdir);
if (!rows[i].byteid) { unlink(src); continue; }
char cs_s[64], ws_s[64];
snprintf(cs_s, sizeof cs_s, "/tmp/wwsca_%d_%d_cs.s",
getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/wwsca_%d_%d_ww.s",
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++; unlink(src); 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++; unlink(src); unlink(cs_s); 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++;
}
unlink(src); unlink(cs_s); unlink(ws_s);
}
if (fail) {
fprintf(stderr, "%d/%d slicecopy-assign tests failed\n",
fail, n);
return 1;
}
printf("slicecopy: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,204 +0,0 @@
/*
* 989_globslicefield_run — F8-c1 (report-item #35): a SLICE field of a
* module-GLOBAL struct, assigned with `g.field = <slice>`, must store the
* full {ptr,len,cap} header — both stages, byte-identical and correct.
*
* THE BUG (cat-A silent miscompile, align-UP): in cgenexpr.ww the
* single-dot global-struct field-assign block (`g.f = v`, base is a direct
* global struct ident) had a str arm that staged the base in DX and stored
* all three header words, but it gated on isstrtype ONLY — a non-str slice
* field ([]i64, []int, ...) fell through to the generic 1-word scalar store
* (`LEAQ g(SB),BX; MOVQ AX,(BX)`), silently dropping .len and .cap. cstage
* (cmd/w6c/cgen.c:5055) gates the same arm on TY_STR||TY_SLICE, so it stored
* the full header and ran correct — the cat-A divergence. The bootstrap
* corpus never assigns a non-str slice to a global-struct field, so 990-997
* stayed green; a runtime row is the net. THE FIX: the wwstage gate widens
* to isstrtype||isslicetype (aligning UP to cstage); a slice rides the
* existing, already-correct 3-word DX store.
*
* Rows (each builds+runs on cstage `ww` and, when present, wwstage `ww_ww`;
* rule-10 — both stages agree AND hit want_exit):
* row | shape | want
* ---------------+---------------------------------------------+-----
* glob_slice_len | g:{sl:[]i64}; g.sl=b[0:3]; len(g.sl) | 3 [#35]
* glob_slice_cap | same; g.sl.cap (b:[4]i64 → cap 4) | 4 [#35]
* glob_off_len | g:{pad:i64,sl:[]i64}; sl@foff 8; len(g.sl) | 2 [#35:
* | pins the DX+foff+8 header store] |
* glob_str_len | g:{name:str}; g.name="hello"; len(g.name) | 5 (str-arm
* | regression pin — the pre-existing path) |
* glob_scalar_n | g:{n:i64}; g.n=7; g.n (scalar field control) | 7 (the
* | 1-word store the slice arm must not steal) |
*
* Pre-fix wwstage: glob_slice_len/cap/off_len all read 0 for the dropped
* words (the 1-word store); cstage and the str/scalar rows were correct.
*/
#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_exit;
};
static const struct row rows[] = {
{ "glob_slice_len",
"package main;\n"
"type box = struct { sl: []i64 };\n"
"let g: box;\n"
"export fn main() int = {\n"
" let b: [4]i64 = [10, 11, 12, 13];\n"
" g.sl = b[0:3];\n"
" return len(g.sl): int;\n"
"};\n",
3 },
{ "glob_slice_cap",
"package main;\n"
"type box = struct { sl: []i64 };\n"
"let g: box;\n"
"export fn main() int = {\n"
" let b: [4]i64 = [10, 11, 12, 13];\n"
" g.sl = b[0:3];\n"
" return g.sl.cap: int;\n"
"};\n",
4 },
/* slice field at NON-ZERO field offset (pad: i64 @0, sl @8). Pins
* the DX+foff+8 / +16 header store. */
{ "glob_off_len",
"package main;\n"
"type box = struct { pad: i64, sl: []i64 };\n"
"let g: box;\n"
"export fn main() int = {\n"
" let b: [4]i64 = [10, 11, 12, 13];\n"
" g.pad = 99;\n"
" g.sl = b[0:2];\n"
" return len(g.sl): int;\n"
"};\n",
2 },
/* str field on a global struct — the pre-existing isstrtype arm; a
* regression pin that the widened gate keeps it byte-identical. */
{ "glob_str_len",
"package main;\n"
"type box = struct { name: str };\n"
"let g: box;\n"
"export fn main() int = {\n"
" g.name = \"hello\";\n"
" return len(g.name): int;\n"
"};\n",
5 },
/* scalar field on a global struct — the generic 1-word store the
* slice arm must not divert (control). */
{ "glob_scalar_n",
"package main;\n"
"type box = struct { n: i64 };\n"
"let g: box;\n"
"export fn main() int = {\n"
" g.n = 7;\n"
" return g.n: int;\n"
"};\n",
7 },
};
/* run_build — build+run `src` via `driver`; returns the binary's exit
* code, or -1 on a build failure. */
static int
run_build(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/gslf_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/gslf_%d_d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -2;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
tmpdir, driver, src);
int brc = runwait(cmd);
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = -1;
if (brc == 0) got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *drv; 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].drv, X_OK) != 0) {
fprintf(stderr, "globslicefield_run: skip %s (no %s)\n",
drivers[d].name, drivers[d].drv);
continue;
}
for (int i = 0; i < n; i++) {
total++;
int got = run_build(drivers[d].drv, &rows[i], i);
if (got != rows[i].want_exit) {
fprintf(stderr, "globslicefield_run[%s][%s]: exit=%d "
"want=%d\n", drivers[d].name, rows[i].label,
got, rows[i].want_exit);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "globslicefield_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("globslicefield_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,166 +0,0 @@
/*
* 989_globstrslice_run — F8-c2 (report-item #47): a module-GLOBAL str
* sliced with a DEFAULT high bound and passed as a call arg (`take(g[1:])`)
* must load its length word for the hi — both stages, byte-identical.
*
* THE BUG (cat-A silent miscompile, align-UP): in cgenutil.ww the N_SLICE
* call-arg pusharg builds {cap,len,ptr} on the stack. For a DEFAULT hi
* (`g[lo:]`) it picks the base length per base kind. The global-base branch
* handled N_TARRAY and N_TSLICE but had NO arm for a global str (N_TNAME
* "str"), so AX kept the base pointer (loaded for the base push) and got
* PUSHQ'd as the hi → len = ptr - lo, garbage. The LOCAL-str arm and the
* alias-NAMED (bu60) arm already loaded the +8 length word; only the plain
* global-str arm was missing. cstage pushargs reads the base type via
* type_chase_named uniformly (cmd/w6c/cgen.c N_SLICE), so it loaded the len
* and ran correct — the cat-A divergence (990-997 never slice a global str
* arg with a default hi, so they stayed green).
*
* THE FIX: the global default-hi branch grows an N_TNAME "str" arm emitting
* the same `LEAQ g(SB),CX; MOVQ 8(CX),AX` as the global N_TSLICE arm (str IS
* []u8 — the len word sits at +8). align ww UP; the .s is byte-identical.
*
* Rows (build+run on cstage `ww` and wwstage `ww_ww`; rule-10 — agree+hit):
* row | shape | want
* ----------------+-------------------------------+-----
* gstr_defhi | slen(g[1:]) g="hello" | 4 [#47 bug — len ptr-lo]
* gstr_defhi_full | slen(g[0:]) lo=0 default hi | 5 [#47 bug]
* gstr_defhi_ptr | sfirst(g[1:]) s[0]=='e' | 101 [ptr word still ok]
* gstr_explicit | slen(g[1:3]) hi explicit | 2 (control: hi!=nil path
* | | must not regress)
*/
#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_exit;
};
#define PRELUDE \
"package main;\n" \
"let g: str = \"hello\";\n" \
"fn slen(s: str) int = { return len(s): int; };\n" \
"fn sfirst(s: str) int = { return s[0]: int; };\n"
static const struct row rows[] = {
{ "gstr_defhi",
PRELUDE
"export fn main() int = { return slen(g[1:]); };\n",
4 },
{ "gstr_defhi_full",
PRELUDE
"export fn main() int = { return slen(g[0:]); };\n",
5 },
{ "gstr_defhi_ptr",
PRELUDE
"export fn main() int = { return sfirst(g[1:]); };\n",
101 },
{ "gstr_explicit",
PRELUDE
"export fn main() int = { return slen(g[1:3]); };\n",
2 },
};
/* run_build — build+run `src` via `driver`; returns the binary's exit
* code, or -1 on a build failure. */
static int
run_build(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/gss_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/gss_%d_d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -2;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
tmpdir, driver, src);
int brc = runwait(cmd);
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = -1;
if (brc == 0) got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *drv; 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].drv, X_OK) != 0) {
fprintf(stderr, "globstrslice_run: skip %s (no %s)\n",
drivers[d].name, drivers[d].drv);
continue;
}
for (int i = 0; i < n; i++) {
total++;
int got = run_build(drivers[d].drv, &rows[i], i);
if (got != rows[i].want_exit) {
fprintf(stderr, "globstrslice_run[%s][%s]: exit=%d "
"want=%d\n", drivers[d].name, rows[i].label,
got, rows[i].want_exit);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "globstrslice_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("globstrslice_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,168 +0,0 @@
/*
* 989_trystr_run (#16) — the `?`/`!` unwrap of a str-success tagged union
* must shuffle the str header (DX,CX,R8) → (AX,BX,CX) for ANY operand shape
* and ANY variant order, keyed off the STAMPED operand type.
*
* THE BUG (wwstage only): cgtryprop/cgtryunw computed succisstr ONLY when the
* operand was an N_CALL, via a name-keyed fnretlookupmod on the callee leaf,
* and tested the FIRST variant (not the success variant). So an ident-source
* unwrap (`let r = mk(); r!`) and an error-first union (`(e|str)`) both
* dropped the `MOVQ CX,BX / MOVQ R8,CX` shuffle — the unwrapped str kept a
* stale len/cap. cstage reads success_is_str = type_isstr(succ_t) at
* cg_tagged_success_tag from the stamped operand type. THE FIX: read the
* stamped operand's success-variant type via successvariant()+typeisstr().
*
* row | shape | exit (cs==ww)
* ----------------+----------------------------------------+--------------
* ident_source | let r=mk(); junk; r! → s.len | 2 (was ww 40)
* errfirst_call | (e|str) mk2()! → s.len | 3
* succfirst_call | (str|e) mk()! (control) | 4
* A 40-byte `junk` str precedes the unwrap so a dropped shuffle leaks junk's
* len into s.len: pre-fix ident_source ran ww=40 vs cs=2 (silent overrun).
*/
#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_exit; };
static const struct row rows[] = {
{ "ident_source",
"package main;\n"
"type e = !i32;\n"
"fn mk() (str | e) = { return \"hi\"; };\n"
"export fn main() i32 = {\n"
" let r: (str | e) = mk();\n"
" let junk: str = \"0123456789012345678901234567890123456789\";\n"
" let s: str = r!;\n"
" return s.len: i32;\n"
"};\n",
2 },
{ "errfirst_call",
"package main;\n"
"type e = !i32;\n"
"fn mk2() (e | str) = { return \"abc\"; };\n"
"export fn main() i32 = {\n"
" let junk: str = \"0123456789012345678901234567890123456789\";\n"
" let s3: str = mk2()!;\n"
" return s3.len: i32;\n"
"};\n",
3 },
{ "succfirst_call",
"package main;\n"
"type e = !i32;\n"
"fn mk() (str | e) = { return \"wxyz\"; };\n"
"export fn main() i32 = {\n"
" let junk: str = \"0123456789012345678901234567890123456789\";\n"
" let s: str = mk()!;\n"
" return s.len: i32;\n"
"};\n",
4 },
};
static int
run_build(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/trystr_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/trystr_%d_d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -2;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
tmpdir, driver, src);
int brc = runwait(cmd);
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = -1;
if (brc == 0) got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
int gc = run_build(cdrv, &rows[i], i);
if (gc < 0) {
fprintf(stderr, "trystr[cstage][%s]: build/run failed "
"(got %d)\n", rows[i].label, gc);
fail++;
continue;
}
if (gc != rows[i].want_exit) {
fprintf(stderr, "trystr[cstage][%s]: exit=%d want=%d\n",
rows[i].label, gc, rows[i].want_exit);
fail++;
}
if (!have_ww) {
fprintf(stderr, "trystr: skip wwstage (no %s)\n", wdrv);
continue;
}
int gw = run_build(wdrv, &rows[i], i);
if (gw != gc) {
fprintf(stderr, "trystr[%s]: cs=%d != ww=%d "
"(str-success unwrap shuffle dropped — #16)\n",
rows[i].label, gc, gw);
fail++;
}
if (gw != rows[i].want_exit) {
fprintf(stderr, "trystr[wwstage][%s]: exit=%d want=%d\n",
rows[i].label, gw, rows[i].want_exit);
fail++;
}
}
if (fail) {
fprintf(stderr, "trystr_run: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("trystr_run: %d/%d ok\n", total, total);
return 0;
}