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:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user