test: migrate 24 residual carriers into the fixture corpus
Audit-driven migrate-and-retire wave (one read-only auditor per carrier batch, verdicts row-checked through the full corpus gate): every retired carrier's assertions are now owned by declarative fixtures or an existing owner. 238 new fixtures land (corpus 1,239 -> 1,477; cells 2,954), covering enum/str/slice/tagged array elements, inferred-length arrays, global slice/str zero-init and literals, global array/pointer field reads and stores, struct-literal slice fields, struct returns, float arithmetic, size/int limits, sort and log-vstream behavior, place-addressed stores, and append places. Rejection rows STRENGTHEN the old nonzero-exit checks to required diagnostic fragments; all byte-id claims fold into the blanket test-data-byteid sweep (now 1,142 compared, 0 pinned-divergent). Four carriers were fully redundant with existing fixtures/lang tests (749, 722, 765, and 771's byteid rows) and retire without new rows. Native carriers 171 -> 147 (residual 135 -> 111).
This commit is contained in:
@@ -32,13 +32,13 @@ categories out of the ordinary developer target.
|
||||
| Fixed point and self-host | `test-bootstrap` |
|
||||
| Host linker/platform behavior | `test-platform` |
|
||||
|
||||
The live declarative compiler corpus has 1,229 fixtures and 2,458 C/WW cells:
|
||||
314 expected rejections (290 shared and 24 stage-specific), 12 compile-only
|
||||
successes, 137 exit-zero programs, and 766 explicit-exit programs.
|
||||
The live declarative compiler corpus has 1,477 fixtures and 2,954 C/WW cells:
|
||||
335 expected rejections (311 shared and 24 stage-specific), 17 compile-only
|
||||
successes, 191 exit-zero programs, and 934 explicit-exit programs.
|
||||
|
||||
171 native C carriers remain. They are partitioned exactly once as five
|
||||
147 native C carriers remain. They are partitioned exactly once as five
|
||||
in-process units, 24 byte/artifact gates, six bootstrap gates, one platform
|
||||
gate, and 135 residual compiler, package-layout, ABI, diagnostic-observer,
|
||||
gate, and 111 residual compiler, package-layout, ABI, diagnostic-observer,
|
||||
driver, linker, or FFI gates. Rows migrated to fixtures or native `@test`
|
||||
owners were removed from those carriers; there is no compatibility execution
|
||||
path for retired rows. 61 former byte/artifact carriers whose only assertion
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package wwfixture;
|
||||
|
||||
def protocolversion: i32 = 1;
|
||||
def corpuscount: i32 = 1239;
|
||||
def errorcount: i32 = 314;
|
||||
def compilecount: i32 = 12;
|
||||
def runcount: i32 = 146;
|
||||
def runexitcount: i32 = 767;
|
||||
def nativecount: i32 = 2478;
|
||||
def corpushash: str = "369aca2f1c0081faf7297d8fa976a7374d97a07b76d2df079f7eea60660bda2a";
|
||||
def corpuscount: i32 = 1477;
|
||||
def errorcount: i32 = 335;
|
||||
def compilecount: i32 = 17;
|
||||
def runcount: i32 = 191;
|
||||
def runexitcount: i32 = 934;
|
||||
def nativecount: i32 = 2954;
|
||||
def corpushash: str = "64258f4de9ac7d71c4ad6221dd2ae7338a38cd5c620fc4ce22c1105021559e83";
|
||||
|
||||
type directive = enum i32 {
|
||||
ERROR = 0,
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
/*
|
||||
* 682_arr_enum_elem — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, on `[N]enum` element access (task #8).
|
||||
*
|
||||
* The bug: wwstage's elemsizeofc (selfhost/cmd/wcc/cgenutil.ww) was the
|
||||
* odd-one-out among the elem*c helpers — its siblings elemissignedc /
|
||||
* elemisfloatc read the checker-stamped tinfo (t.type_.sub), but
|
||||
* elemsizeofc derived the element width purely structurally
|
||||
* (elemsizeof -> primsize -> slotsize). A named-narrow element
|
||||
* (`[N]tk`, tk = enum i32) is not a builtin prim, so primsize returned
|
||||
* 0 and elemsizeofc fell through to a raw 8-byte slot instead of the
|
||||
* enum's i32 backing (4). That mis-sized BOTH manifestations through
|
||||
* the one choke-point:
|
||||
* (a) cgindex READ — `a[i]` strode by 8 (MOVQ) where cstage strode by
|
||||
* 4 (MOVSXD), reading the wrong/out-of-bounds element for i>=1.
|
||||
* (b) array-init STORE — a local `[N]enum` literal init stored at
|
||||
* stride 8 into a stride-4 frame slot, overrunning the slot and
|
||||
* smashing the frame -> wwstage-built binary SEGFAULTED.
|
||||
* cstage is runtime-correct (cgen.c N_INDEX idx_eff(bt)->sub->size,
|
||||
* N_LET array-init lu->sub->size, cgen.c:6387). The fix aligns wwstage
|
||||
* UP: elemsizeofc now reads the element size off the stamped tinfo
|
||||
* (peeling TY_NAMED), recovering 4 for `enum i32` and bringing all four
|
||||
* elem*c helpers onto the same tinfo SSoT.
|
||||
*
|
||||
* No in-tree `[N]enum` / aliased-narrow element existed before kwtab,
|
||||
* which is why this was byte-id-gate-blind until now.
|
||||
*
|
||||
* row | shape | want
|
||||
* -----------------+------------------------------------+--------------
|
||||
* global_idx0 | global [4]tk, return g[0]. idx0 is | 7 + byte-id
|
||||
* | stride-independent (offset 0). |
|
||||
* global_read | global [4]tk, return g[2]. Pre-fix | 99 + byte-id
|
||||
* | stride-8 read offset 16 = past the |
|
||||
* | 16-byte array -> garbage. |
|
||||
* local_read | LOCAL [4]tk init, return a[1]. | 21 + byte-id
|
||||
* | Exercises the array-init STORE + |
|
||||
* | the stride-4 READ together. |
|
||||
* local_init_sum | LOCAL [4]tk, return a[0]+a[3]. | 10 + byte-id
|
||||
* | First+last element after init. |
|
||||
* local_signed | LOCAL [2]tk with a negative enum | 251 (-5)
|
||||
* | value, return a[0]:i32. Pins the | + byte-id
|
||||
* | MOVSXD sign-extend at stride 4 |
|
||||
* | (pre-fix MOVQ read 8 bytes). |
|
||||
* frame_smash | LOCAL [6]tk init + read a[5]. | 5 + byte-id
|
||||
* | Pre-fix stride-8 store writes 48 |
|
||||
* | bytes into a 24-byte slot -> |
|
||||
* | frame smash / SEGFAULT under |
|
||||
* | wwstage. Correct stride-4 store |
|
||||
* | (24 bytes) round-trips a[5]=5. |
|
||||
* width1_u8 | LOCAL [3]tu (enum u8), read a[1]. | 200 + byte-id
|
||||
* | Pins the narrow-scalar class at |
|
||||
* | width 1: stride-1 store + MOVZBQ |
|
||||
* | zero-extend load (UNSIGNED enum). |
|
||||
* | Pre-fix stride-8 store/read over- |
|
||||
* | runs the 3-byte slot -> garbage. |
|
||||
* width2_i16 | LOCAL [3]ts (enum i16) w/ negative | 251 (-5)
|
||||
* | value, read a[1]:i32. Pins width 2:| + byte-id
|
||||
* | stride-2 store + MOVSWQ sign- |
|
||||
* | extend (pre-fix stride-8 garbage). |
|
||||
*
|
||||
* width1/width2 widen the coverage from the single enum-i32 (width 4)
|
||||
* case to the whole {1,2,4} narrow-scalar class the fix closes, and add
|
||||
* the unsigned (MOVZBQ) load path alongside the signed (MOVSXD/MOVSWQ).
|
||||
*
|
||||
* Exit-code rows confirm both stages run correctly. The asm-byte-id
|
||||
* rows pin the symmetric stride-4 MOVSXD emit (cstage == wwstage);
|
||||
* pre-fix wwstage emitted stride-8 MOVQ, so the diff was non-empty.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "global_idx0",
|
||||
"type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };\n"
|
||||
"let g: [4]tk = [tk.A, tk.B, tk.C, tk.D];\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\treturn g[0]: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "global_read",
|
||||
"type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };\n"
|
||||
"let g: [4]tk = [tk.A, tk.B, tk.C, tk.D];\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\treturn g[2]: i32;\n"
|
||||
"};\n",
|
||||
99 },
|
||||
|
||||
{ "local_read",
|
||||
"type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet a: [4]tk = [tk.A, tk.B, tk.C, tk.D];\n"
|
||||
"\treturn a[1]: i32;\n"
|
||||
"};\n",
|
||||
21 },
|
||||
|
||||
{ "local_init_sum",
|
||||
"type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet a: [4]tk = [tk.A, tk.B, tk.C, tk.D];\n"
|
||||
"\treturn (a[0]: i32) + (a[3]: i32);\n"
|
||||
"};\n",
|
||||
10 },
|
||||
|
||||
{ "local_signed",
|
||||
"type tk = enum i32 { A = 7i32, G = -5i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet a: [2]tk = [tk.G, tk.A];\n"
|
||||
"\treturn a[0]: i32;\n"
|
||||
"};\n",
|
||||
251 },
|
||||
|
||||
{ "frame_smash",
|
||||
"type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32,\n"
|
||||
"\tD = 3i32, E = 11i32, F = 5i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet a: [6]tk = [tk.A, tk.B, tk.C, tk.D, tk.E, tk.F];\n"
|
||||
"\treturn a[5]: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
{ "width1_u8",
|
||||
"type tu = enum u8 { A = 7u8, B = 200u8, C = 3u8 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet a: [3]tu = [tu.A, tu.B, tu.C];\n"
|
||||
"\treturn a[1]: i32;\n"
|
||||
"};\n",
|
||||
200 },
|
||||
|
||||
{ "width2_i16",
|
||||
"type ts = enum i16 { A = 7i16, G = -5i16, C = 99i16 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet a: [3]ts = [ts.A, ts.G, ts.C];\n"
|
||||
"\treturn a[1]: i32;\n"
|
||||
"};\n",
|
||||
251 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], scratch[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/aee_%d_d_%d", getpid(), i);
|
||||
snprintf(src, sizeof src, "%s/aee_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/aee_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
perror(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
int result = -1;
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { perror(src); goto cleanup; }
|
||||
wwtest_fputs(r->src, f);
|
||||
if (fclose(f) != 0) { perror(src); goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = runwait(outbin);
|
||||
|
||||
cleanup:
|
||||
/* `ww build -o` retains this exact caller-owned scratch tree. */
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", scratch);
|
||||
int cleanfail = runwait(cmd) != 0;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) {
|
||||
perror(outbin); cleanfail = 1;
|
||||
}
|
||||
if (unlink(src) != 0 && errno != ENOENT) {
|
||||
perror(src); cleanfail = 1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
perror(tmpdir); cleanfail = 1;
|
||||
}
|
||||
if (cleanfail && result == r->want) result = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. This is the regression-pinning row for #8: pre-fix
|
||||
* wwstage emitted stride-8 MOVQ where cstage emitted stride-4 MOVSXD,
|
||||
* so the diff was non-empty. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], cs[128], ws[128], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/aee_asm_%d_%d", getpid(), i);
|
||||
snprintf(src, sizeof src, "%s/input.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
perror(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
int rc = -1;
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { perror(src); goto cleanup; }
|
||||
wwtest_fputs(r->src, f);
|
||||
if (fclose(f) != 0) { perror(src); goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) {
|
||||
perror(src); rc = -1;
|
||||
}
|
||||
if (unlink(cs) != 0 && errno != ENOENT) {
|
||||
perror(cs); rc = -1;
|
||||
}
|
||||
if (unlink(ws) != 0 && errno != ENOENT) {
|
||||
perror(ws); rc = -1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
perror(tmpdir); rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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, "arr_enum_elem: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"arr_enum_elem[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_enum_elem: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_enum_elem: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
/*
|
||||
* 683_arr_strslice_elem — cstage and wwstage agree, byte-for-byte and
|
||||
* at runtime, that an `[N][]u8` / `[N]str` array-LITERAL init copies the
|
||||
* FULL 24B {ptr,len,cap} header of every element (task #20, the #270
|
||||
* aggregate-element-store family's str/slice arm).
|
||||
*
|
||||
* The bug: cgen's N_LET / N_ARRLIT per-element store lowered each str/
|
||||
* slice element's header into AX=ptr/BX=len/CX=cap (cgexpr) but stored
|
||||
* only some words — a slice element fell through to the scalar 1-word
|
||||
* MOVQ (dropping .len AND .cap), and a str element stored 2 words
|
||||
* (dropping .cap, latent). Each element is 24B and must be copied
|
||||
* whole. wwstage was worse: a slice element matched no esz branch, so
|
||||
* esz stayed the 8 sentinel — the per-element stride collapsed (element
|
||||
* i+1 overwrote element i's tail), the -96-vs-80 cs!=ww frame
|
||||
* divergence. struct/array/tuple elements already copied correctly via
|
||||
* the #270-1c is_agg multi-word path; str/slice were the documented
|
||||
* follow-up (cgen.c:9037-9042, cgenstmt.ww deferral comment).
|
||||
*
|
||||
* The fix (BOTH stages, converged byte-identical): cstage adds
|
||||
* is_slice_el = type_isslice(esub) and stores 3 words (incl CX->base+16)
|
||||
* for `is_str_el || is_slice_el`; wwstage adds isslicel (TY_SLICE ->
|
||||
* esz = esubti.size, fixing the stride) and the matching 3-word store.
|
||||
*
|
||||
* Cap is validated BOTH ways: a WHOLE-ELEMENT COPY (`let q = t[i]; q.cap`)
|
||||
* AND a DIRECT `t[i].cap` read. The direct read was a SEPARATE bug (task
|
||||
* #13, the #20 store's read-sibling): the `.cap` field-extract on an
|
||||
* INDEXED slice/str element returned .ptr on cstage (it shuffled only
|
||||
* .len BX→AX, leaving AX=.ptr for cap) and emitted NO read on wwstage
|
||||
* (the cgdot non-ident catch-all handled only .ptr/.len) — divergent.
|
||||
* Fix (BOTH stages, byte-id): cgexpr leaves the full {ptr,len,cap} header
|
||||
* via cgslicehdr for an indexed element, so .cap shuffles CX→AX, the twin
|
||||
* of the .len BX→AX shuffle. The direct-cap rows must equal the
|
||||
* whole-element-copy oracle (which #20 made correct).
|
||||
*
|
||||
* Mutation-sanity (the per-word coverage): the .len rows fail if the
|
||||
* store drops the .len word (the pre-fix slice 1-word store), and the
|
||||
* cap-via-copy rows fail if it drops the .cap word (the pre-fix str
|
||||
* 2-word store) — so a regression to a 1-word or 2-word store is caught.
|
||||
*
|
||||
* row | shape | want
|
||||
* -----------------+------------------------------------+--------------
|
||||
* slice_len1 | [2][]u8, return t[1].len. Pre-fix | 2 + byte-id
|
||||
* | slice 1-word store dropped .len. |
|
||||
* slice_len0 | [2][]u8, return t[0].len. | 3 + byte-id
|
||||
* slice_stride3 | [3][]u8, return t[2].len. Exercises| 7 + byte-id
|
||||
* | the 24B per-element stride (the |
|
||||
* | wwstage 8-sentinel frame-offset |
|
||||
* | bug overran into the wrong slot). |
|
||||
* slice_cap_copy | [2][]u8 w/ caps 7,6; let q=t[0]; | 7 + byte-id
|
||||
* | return q.cap. Validates the stored |
|
||||
* | .cap word (pre-fix dropped). |
|
||||
* slice_ptr | [2][]u8 over a backing array; let | 4 + byte-id
|
||||
* | q=t[1]; return q[0]. Pins .ptr |
|
||||
* | stored correctly (deref the elem). |
|
||||
* str_len1 | [2]str=[a,b], return t[1].len. | 2 + byte-id
|
||||
* str_cap_copy | [2]str=["abcde","xy"]; let q=t[0]; | 5 + byte-id
|
||||
* | return q.cap. The latent str cap- |
|
||||
* | drop (pre-fix garbage); cap=len=5 |
|
||||
* | for a static literal. |
|
||||
* struct_elem | [2]Pt struct literal, return | 4 + byte-id
|
||||
* | t[1].y. Regression pin: the is_agg |
|
||||
* | multi-word path is untouched by |
|
||||
* | the str/slice branch. |
|
||||
*
|
||||
* Exit-code rows confirm both stages run correctly. The asm-byte-id rows
|
||||
* pin the symmetric 3-word store (cstage == wwstage); pre-fix wwstage
|
||||
* mis-strided (slice esz=8) and under-copied, so the diff was non-empty.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* want == BYTEID_ONLY: the row's runtime value is non-deterministic (a
|
||||
* link-time address), so only the cstage/wwstage asm-byte-id is asserted;
|
||||
* the build must still succeed on both stages. Used for the `"abc".cap`
|
||||
* symmetry edge (a string-literal .cap is meaningless garbage on both
|
||||
* stages, but rule 10 still requires byte-identical asm). */
|
||||
#define BYTEID_ONLY (-2147483647 - 1)
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "slice_len1",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 8;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\treturn t[1].len: i32;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
{ "slice_len0",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 8;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\treturn t[0].len: i32;\n"
|
||||
"};\n",
|
||||
3 },
|
||||
|
||||
{ "slice_stride3",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 5; b.cap = 8;\n"
|
||||
"\tlet c: []u8; c.ptr = &hb[0]; c.len = 7; c.cap = 8;\n"
|
||||
"\tlet t: [3][]u8 = [a, b, c];\n"
|
||||
"\treturn t[2].len: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "slice_cap_copy",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\tlet q: []u8 = t[0];\n"
|
||||
"\treturn q.cap: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
/* #13 direct-read rows: `t[i].cap` straight (no copy via a let).
|
||||
* Must equal the slice_cap_copy / str_cap_copy oracle. Pre-fix:
|
||||
* cstage returned .ptr (a heap/stack address, != 7/6), wwstage
|
||||
* emitted no read (stale AX) — and the two diverged. */
|
||||
{ "slice_cap_direct0",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\treturn t[0].cap: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "slice_cap_direct1",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\treturn t[1].cap: i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
|
||||
{ "str_cap_direct0",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: str = \"abcde\";\n"
|
||||
"\tlet b: str = \"xy\";\n"
|
||||
"\tlet t: [2]str = [a, b];\n"
|
||||
"\treturn t[0].cap: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
{ "str_cap_direct1",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: str = \"abcde\";\n"
|
||||
"\tlet b: str = \"xy\";\n"
|
||||
"\tlet t: [2]str = [a, b];\n"
|
||||
"\treturn t[1].cap: i32;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* #13 symmetry edge: `.cap` of a BARE string literal. The wwstage
|
||||
* checker types N_STRLIT as `str` (cstage types it untyped_str), so
|
||||
* the .cap kind-gate would wrongly shuffle CX→AX on wwstage only —
|
||||
* but a literal's cgexpr never loads a CX cap, and cstage never
|
||||
* shuffles it, so both must return AX (ptr) unshuffled. Byte-id only:
|
||||
* the value is a link-time address (non-deterministic). Pre-fix this
|
||||
* row's asm DIFFERED (wwstage had the stray MOVQ CX, AX). */
|
||||
{ "str_lit_cap_symmetry",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn \"abc\".cap: i32;\n"
|
||||
"};\n",
|
||||
BYTEID_ONLY },
|
||||
|
||||
{ "slice_ptr",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8; hb[0] = 9u8; hb[1] = 4u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[1]; b.len = 2; b.cap = 8;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\tlet q: []u8 = t[1];\n"
|
||||
"\treturn q[0]: i32;\n"
|
||||
"};\n",
|
||||
4 },
|
||||
|
||||
{ "str_len1",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: str = \"abc\";\n"
|
||||
"\tlet b: str = \"de\";\n"
|
||||
"\tlet t: [2]str = [a, b];\n"
|
||||
"\treturn t[1].len: i32;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
{ "str_cap_copy",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: str = \"abcde\";\n"
|
||||
"\tlet b: str = \"xy\";\n"
|
||||
"\tlet t: [2]str = [a, b];\n"
|
||||
"\tlet q: str = t[0];\n"
|
||||
"\treturn q.cap: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* Regression pin: a [N]struct element copies multi-word via the
|
||||
* pre-existing #270-1c is_agg path, NOT the new str/slice 3-word
|
||||
* header branch. Proves the str/slice esz/store change leaves the
|
||||
* is_agg element path untouched (byte-id holds for it too). */
|
||||
{ "struct_elem",
|
||||
"package main;\n"
|
||||
"type Pt = struct { x: i32, y: i32 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [2]Pt = [Pt { x = 1, y = 2 }, Pt { x = 3, y = 4 }];\n"
|
||||
"\treturn t[1].y: i32;\n"
|
||||
"};\n",
|
||||
4 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], scratch[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/asse_%d_d_%d", getpid(), i);
|
||||
snprintf(src, sizeof src, "%s/asse_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/asse_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
perror(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
int result = -1;
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { perror(src); goto cleanup; }
|
||||
fputs(r->src, f);
|
||||
if (fclose(f) != 0) { perror(src); goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = runwait(outbin);
|
||||
|
||||
cleanup:
|
||||
/* `ww build -o` retains this exact caller-owned scratch tree. */
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", scratch);
|
||||
int cleanfail = runwait(cmd) != 0;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) {
|
||||
perror(outbin); cleanfail = 1;
|
||||
}
|
||||
if (unlink(src) != 0 && errno != ENOENT) {
|
||||
perror(src); cleanfail = 1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
perror(tmpdir); cleanfail = 1;
|
||||
}
|
||||
if (cleanfail && (r->want == BYTEID_ONLY || result == r->want))
|
||||
result = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. The regression-pinning row for #20: pre-fix wwstage
|
||||
* mis-strided the slice element (esz=8) and under-copied, so the diff
|
||||
* was non-empty; the converged 3-word store makes them identical. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], cs[128], ws[128], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/asse_asm_%d_%d", getpid(), i);
|
||||
snprintf(src, sizeof src, "%s/input.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
perror(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
int rc = -1;
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { perror(src); goto cleanup; }
|
||||
fputs(r->src, f);
|
||||
if (fclose(f) != 0) { perror(src); goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) {
|
||||
perror(src); rc = -1;
|
||||
}
|
||||
if (unlink(cs) != 0 && errno != ENOENT) {
|
||||
perror(cs); rc = -1;
|
||||
}
|
||||
if (unlink(ws) != 0 && errno != ENOENT) {
|
||||
perror(ws); rc = -1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
perror(tmpdir); rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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, "arr_strslice_elem: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
/* BYTEID_ONLY rows assert asm byte-id below; here only
|
||||
* the build must succeed (run_driver returns -1 on a
|
||||
* build/run failure). The exit value is ignored. */
|
||||
int bad = rows[i].want == BYTEID_ONLY
|
||||
? (got == -1) : (got != rows[i].want);
|
||||
if (bad) {
|
||||
fprintf(stderr,
|
||||
"arr_strslice_elem[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_strslice_elem: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_strslice_elem: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
/*
|
||||
* 684_arr_infer_len — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, that a `[_]T = [...]` array infers its length from the
|
||||
* initialiser's element count (task #7, a canonical Hare form).
|
||||
*
|
||||
* The bug: `[_]T` silently miscompiled to a zero-length array. The
|
||||
* parser already left the array type's length child nil as the infer
|
||||
* sentinel (distinct from an explicit `[N]`), but neither checker
|
||||
* stamped the real count — so `len(x)` / `x.len` returned 0 with no
|
||||
* diagnostic (rule-7 silent miscompile). Module-level was worse on
|
||||
* wwstage: `x.len` on ANY global array (even explicit `[N]`) fell to
|
||||
* the SB fallback and mis-emitted `MOVQ len(SB), AX` (linker:
|
||||
* undefined reference to len).
|
||||
*
|
||||
* The fix (BOTH stages, converged byte-identical):
|
||||
* - checker (cmd/wcc/check.c clet + module-level N_LET;
|
||||
* selfhost/cmd/wcc/check.ww inferarraylen): count the array-literal
|
||||
* elements and stamp the length onto the array TYPE. cgen already
|
||||
* keys stride/length/data off the stamped length, so it "just
|
||||
* works" (rob's #7 ruling). A `[_]T` with no array-literal init
|
||||
* can't infer → LOUD error, never a silent zero-length array.
|
||||
* - cgen (selfhost/cmd/wcc/cgenexpr.ww cgdot): a top-level `[N]T`
|
||||
* global's `.len` / `.ptr` pseudo-fields, the wwstage arm that was
|
||||
* missing (cstage cgen.c:8011 already handled it).
|
||||
*
|
||||
* Coverage: `[_]int` / `[_]str` / `[_]u8`, both local and module-level,
|
||||
* `len` read-back and element read-back, dual-stage runtime + asm
|
||||
* byte-id. Mutation-sanity: the old collapse-to-0 fails every `*_len`
|
||||
* row (len would be 0, not the count). Plus three negative rows where
|
||||
* `[_]T` can't infer (no init / non-array init) — both stages must
|
||||
* FAIL the build.
|
||||
*
|
||||
* row | shape | want
|
||||
* ----------------+--------------------------------------+------
|
||||
* local_int_len | local [_]int=[10,20,30,40], x.len | 4
|
||||
* local_int_elem | local, x[2] | 30
|
||||
* mod_int_len | global [_]int=[..], x.len | 4
|
||||
* mod_int_elem | global, x[2] | 30
|
||||
* local_str_len | local [_]str=["a","b","c"], x.len | 3
|
||||
* local_str_elem | local [_]str=["ab","cde"], x[0].len | 2
|
||||
* mod_str_len | global [_]str=["a","b","c"], x.len | 3
|
||||
* local_u8_len | local [_]u8=[1..5], x.len | 5
|
||||
* local_u8_elem | local, x[4] | 5
|
||||
* local_one_len | local [_]int=[7], x.len (1-elem edge)| 1
|
||||
* mod_one_len | global [_]int=[7], x.len | 1
|
||||
* mod_one_elem | global, x[0] | 7
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "local_int_len",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]int = [10, 20, 30, 40];\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
4 },
|
||||
|
||||
{ "local_int_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]int = [10, 20, 30, 40];\n"
|
||||
"\treturn x[2]: i32;\n"
|
||||
"};\n",
|
||||
30 },
|
||||
|
||||
{ "mod_int_len",
|
||||
"package main;\n"
|
||||
"let x: [_]int = [10, 20, 30, 40];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
4 },
|
||||
|
||||
{ "mod_int_elem",
|
||||
"package main;\n"
|
||||
"let x: [_]int = [10, 20, 30, 40];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn x[2]: i32;\n"
|
||||
"};\n",
|
||||
30 },
|
||||
|
||||
{ "local_str_len",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]str = [\"a\", \"b\", \"c\"];\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
3 },
|
||||
|
||||
{ "local_str_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]str = [\"ab\", \"cde\"];\n"
|
||||
"\treturn x[0].len: i32;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
{ "mod_str_len",
|
||||
"package main;\n"
|
||||
"let x: [_]str = [\"a\", \"b\", \"c\"];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
3 },
|
||||
|
||||
{ "local_u8_len",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
{ "local_u8_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
|
||||
"\treturn x[4]: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* 1-element edge — the minimal non-empty count; under the old
|
||||
* collapse-to-0 bug `.len` reads 0, not 1, so this row also fails
|
||||
* the mutation. */
|
||||
{ "local_one_len",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]int = [7];\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
|
||||
{ "mod_one_len",
|
||||
"package main;\n"
|
||||
"let x: [_]int = [7];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn x.len: i32;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
|
||||
{ "mod_one_elem",
|
||||
"package main;\n"
|
||||
"let x: [_]int = [7];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn x[0]: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
/* `[_]T` that can't infer its length — both stages must FAIL the build
|
||||
* (loud diagnostic, not a silent zero-length array). */
|
||||
static const char *neg[] = {
|
||||
/* no init, local */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]str;\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
/* no init, module-level */
|
||||
"package main;\n"
|
||||
"let x: [_]int;\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
/* non-array initialiser */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: [_]int = 5;\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, setupfail = 0, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ail_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/ail_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/ail_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { setupfail = 1; goto cleanup; }
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) { setupfail = 1; goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
if (setupfail)
|
||||
fprintf(stderr, "row[%s]: temporary setup failed\n", r->label);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
if (result == r->want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* build_should_fail — a `[_]T` that can't infer must error on `driver`;
|
||||
* returns 0 when the build correctly FAILS, positive when it wrongly
|
||||
* succeeds, and negative on fixture infrastructure failure. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, setupfail = 0, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ailn_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(s, sizeof s, "%s/ailn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/ailn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { setupfail = 1; goto cleanup; }
|
||||
int writefail = fputs(src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) { setupfail = 1; goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
result = rc == 0 ? 1 : 0; /* build must NOT succeed */
|
||||
cleanup:
|
||||
if (setupfail)
|
||||
fprintf(stderr, "arr_infer_len[neg%d]: temporary setup failed\n", i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(s) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "arr_infer_len[neg%d]: temporary cleanup failed\n", i);
|
||||
if (result == 0) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[96], cs[96], ws[96], cmd[1024];
|
||||
int rc = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ail_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/t.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(ws) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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 nn = (int)(sizeof neg / sizeof neg[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, "arr_infer_len: 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,
|
||||
"arr_infer_len[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
int nr = build_should_fail(drivers[d].path, neg[i], 100 + i);
|
||||
if (nr != 0) {
|
||||
if (nr > 0)
|
||||
fprintf(stderr,
|
||||
"arr_infer_len[%s][neg%d]: built ok, "
|
||||
"expected a loud error\n",
|
||||
drivers[d].name, i);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"arr_infer_len[%s][neg%d]: fixture infrastructure failed\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_infer_len: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_infer_len: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* 685_arr_tagged_elem — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, that a `[N]tagged` array-LITERAL init boxes the FULL tag+
|
||||
* payload slot of every element (task #12, the #270 aggregate-element-
|
||||
* store family's tagged-union arm; sibling of #20's str/slice arm in
|
||||
* test 683).
|
||||
*
|
||||
* The bug: cgen's N_ARRLIT per-element store excluded TY_TAGGED from the
|
||||
* is_agg predicate, so a tagged element fell through to the scalar
|
||||
* 1-word MOVQ — it stored the raw scalar (99) into WORD0 (the TAG slot),
|
||||
* with NO tag word and NO payload placement. A subsequent `match` found
|
||||
* no variant (tag was the payload value, not a variant index) and the
|
||||
* arms silently produced 0. wwstage was worse: esz stayed the 8 sentinel
|
||||
* for a 16B tagged element (the #8-class stride trap), so element i+1
|
||||
* overran element i's tail. The under-copy DEPTH (1 word vs full slot)
|
||||
* is byte-id-blind — both stages stored 1 word, so the asm-byte-id gate
|
||||
* passed green on both-wrong; only the runtime readback below catches it.
|
||||
*
|
||||
* The fix (BOTH stages, converged byte-identical): route each tagged
|
||||
* element through the SAME cg_widen_tagged_store / cgwidentaggedstore
|
||||
* choke-point every other tagged store uses (let-init, vararg gather,
|
||||
* struct-field) — NOT a new is_agg branch (is_agg word-copies/fatals and
|
||||
* never boxes the tag). cstage adds is_tagged_el = esubu->kind ==
|
||||
* TY_TAGGED; wwstage adds istaggedel and sizes esz from the stamped
|
||||
* tinfo (esubti.size, rule-13) so the 16B/24B stride is correct.
|
||||
*
|
||||
* row | shape | want
|
||||
* ----------------+-------------------------------------+-----------
|
||||
* lit_sum106 | [2]cell=[99i64,7i64] (cell= | 106 (ken
|
||||
* | (i64|bool)); match+sum both. | oracle)
|
||||
* lit_first | same; match t[0] only. | 99
|
||||
* lit_second | same; match t[1] only. | 7
|
||||
* bool_variant | [2]cell=[5i64, true]; match t[1]. | 1 pins the
|
||||
* | The bool arm only fires if the tag | tag word
|
||||
* | word was written (not payload-in- | was placed
|
||||
* | word0). Pre-fix: 0 (no tag). |
|
||||
* var_sum | [2]cell=[a,b] (a=40i64,b=2i64 | 42 variable-
|
||||
* | idents); match+sum. Repro B. | source
|
||||
* stride3 | [3]cell=[10i64,20i64,30i64]; match | 30 16B per-
|
||||
* | t[2]. Exercises the per-element | element
|
||||
* | slot stride (wwstage 8-sentinel | stride
|
||||
* | overran into the wrong slot). |
|
||||
* boxed_void | [3](i32|void)=[1i32,2i32,void]; | 13 bare-value
|
||||
* | match: i32 arm sums payload, void | box: tag +
|
||||
* | arm adds 10. 1+2+10. | payload +
|
||||
* | | zero-pad
|
||||
*
|
||||
* The asm-byte-id rows pin the symmetric widen (cstage == wwstage). The
|
||||
* under-copy itself is byte-id-blind (see above) — these rows guard the
|
||||
* stride/op symmetry, the runtime rows guard the correctness.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "lit_sum106",
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [2]cell = [99i64, 7i64];\n"
|
||||
"\tlet sum: i64 = 0i64;\n"
|
||||
"\tlet i: i32 = 0;\n"
|
||||
"\tfor (i < 2) {\n"
|
||||
"\t\tmatch (t[i]) {\n"
|
||||
"\t\tcase let v: i64 => { sum = sum + v; };\n"
|
||||
"\t\tcase let z: bool => { sum = sum + 1i64; };\n"
|
||||
"\t\t};\n"
|
||||
"\t\ti = i + 1;\n"
|
||||
"\t};\n"
|
||||
"\treturn sum: i32;\n"
|
||||
"};\n",
|
||||
106 },
|
||||
|
||||
{ "lit_first",
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [2]cell = [99i64, 7i64];\n"
|
||||
"\tmatch (t[0]) {\n"
|
||||
"\tcase let v: i64 => { return v: i32; };\n"
|
||||
"\tcase let z: bool => { return -1; };\n"
|
||||
"\t};\n"
|
||||
"};\n",
|
||||
99 },
|
||||
|
||||
{ "lit_second",
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [2]cell = [99i64, 7i64];\n"
|
||||
"\tmatch (t[1]) {\n"
|
||||
"\tcase let v: i64 => { return v: i32; };\n"
|
||||
"\tcase let z: bool => { return -1; };\n"
|
||||
"\t};\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "bool_variant",
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [2]cell = [5i64, true];\n"
|
||||
"\tmatch (t[1]) {\n"
|
||||
"\tcase let v: i64 => { return -1; };\n"
|
||||
"\tcase let z: bool => { if (z) { return 1; }; return 0; };\n"
|
||||
"\t};\n"
|
||||
"};\n",
|
||||
1 },
|
||||
|
||||
{ "var_sum",
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: cell = 40i64;\n"
|
||||
"\tlet b: cell = 2i64;\n"
|
||||
"\tlet t: [2]cell = [a, b];\n"
|
||||
"\tlet sum: i64 = 0i64;\n"
|
||||
"\tlet i: i32 = 0;\n"
|
||||
"\tfor (i < 2) {\n"
|
||||
"\t\tmatch (t[i]) {\n"
|
||||
"\t\tcase let v: i64 => { sum = sum + v; };\n"
|
||||
"\t\tcase let z: bool => { sum = sum + 100i64; };\n"
|
||||
"\t\t};\n"
|
||||
"\t\ti = i + 1;\n"
|
||||
"\t};\n"
|
||||
"\treturn sum: i32;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
{ "stride3",
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [3]cell = [10i64, 20i64, 30i64];\n"
|
||||
"\tmatch (t[2]) {\n"
|
||||
"\tcase let v: i64 => { return v: i32; };\n"
|
||||
"\tcase let z: bool => { return -1; };\n"
|
||||
"\t};\n"
|
||||
"};\n",
|
||||
30 },
|
||||
|
||||
{ "boxed_void",
|
||||
"package main;\n"
|
||||
"type ov = (i32 | void);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [3]ov = [1i32, 2i32, void];\n"
|
||||
"\tlet sum: i32 = 0;\n"
|
||||
"\tlet i: i32 = 0;\n"
|
||||
"\tfor (i < 3) {\n"
|
||||
"\t\tmatch (t[i]) {\n"
|
||||
"\t\tcase let v: i32 => { sum = sum + v; };\n"
|
||||
"\t\tcase void => { sum = sum + 10; };\n"
|
||||
"\t\t};\n"
|
||||
"\t\ti = i + 1;\n"
|
||||
"\t};\n"
|
||||
"\treturn sum;\n"
|
||||
"};\n",
|
||||
13 },
|
||||
};
|
||||
|
||||
/* rule-7 loud-stop: `[N]tagged = [x, ...]` repeat-fill is not wired
|
||||
* (cg_widen_tagged_store consumes the node and trashes AX, so the
|
||||
* repeat re-store would write garbage). Both stages must FAIL the
|
||||
* build loudly, not silently emit a broken array. */
|
||||
static const char *neg[] = {
|
||||
"package main;\n"
|
||||
"type cell = (i64 | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [3]cell = [99i64...];\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, setupfail = 0, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/atag_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/atag_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/atag_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { setupfail = 1; goto cleanup; }
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) { setupfail = 1; goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
if (setupfail)
|
||||
fprintf(stderr, "row[%s]: temporary setup failed\n", r->label);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
if (result == r->want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* build_should_fail — a `[N]tagged=[x,...]` repeat-fill must error on
|
||||
* `driver` (rule-7 loud-stop); returns 0 when the build correctly
|
||||
* FAILS, positive when it wrongly succeeds, and negative on fixture
|
||||
* infrastructure failure. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, setupfail = 0, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/atagn_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(s, sizeof s, "%s/atagn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/atagn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { setupfail = 1; goto cleanup; }
|
||||
int writefail = fputs(src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) { setupfail = 1; goto cleanup; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
result = rc == 0 ? 1 : 0; /* build must NOT succeed */
|
||||
cleanup:
|
||||
if (setupfail)
|
||||
fprintf(stderr, "arr_tagged_elem[neg%d]: temporary setup failed\n", i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(s) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "arr_tagged_elem[neg%d]: temporary cleanup failed\n", i);
|
||||
if (result == 0) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. The under-copy depth is byte-id-blind (both stages
|
||||
* stored 1 word pre-fix), so this row guards only the symmetric widen
|
||||
* stride/op; the runtime rows above are the correctness net. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[96], cs[96], ws[96], cmd[1024];
|
||||
int rc = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/atag_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/t.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(ws) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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 nn = (int)(sizeof neg / sizeof neg[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, "arr_tagged_elem: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"arr_tagged_elem[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
int nr = build_should_fail(drivers[d].path, neg[i], 100 + i);
|
||||
if (nr != 0) {
|
||||
if (nr > 0)
|
||||
fprintf(stderr,
|
||||
"arr_tagged_elem[%s][neg%d]: built ok, "
|
||||
"expected a loud error (rule-7)\n",
|
||||
drivers[d].name, i);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"arr_tagged_elem[%s][neg%d]: fixture infrastructure failed\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_tagged_elem: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_tagged_elem: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* 686_slice_str_global_zero — a bare module-level `str` global and a
|
||||
* bare `[]u8` slice global emit their 24-byte zero header EXACTLY ONCE,
|
||||
* and cstage / wwstage agree byte-for-byte (task #10 part b).
|
||||
*
|
||||
* The bug (wwstage only): post-#1, size(str) == size(slice) == 24. The
|
||||
* emitletdataw str arm and slice arm were sequential `if`s gated on
|
||||
* SIZE alone, so both matched a 24-byte bare global and BOTH fired the
|
||||
* no-rhs zero fallback — two `DATAW main.g` rows. cstage discriminates
|
||||
* on type kind (let_isstr / let_isslice) and emits one. Runtime reads
|
||||
* the null header {0,0,0} correctly either way, so the divergence is
|
||||
* byte-id-visible only — the asm_byte_identical gate is the real
|
||||
* discriminator; the exit-code rows assert correctness is preserved.
|
||||
*
|
||||
* The fix: emitletdataw's str and slice arms now branch on the declared
|
||||
* type kind (letdeclkind, TY_NAMED-peeled, mirroring let_isstr/
|
||||
* let_isslice), mutually exclusive, so a 24B global hits one arm.
|
||||
*
|
||||
* row | shape | want
|
||||
* -----------------+----------------------------------------+------
|
||||
* slice_len | global let g:[]u8; g.len | 0
|
||||
* slice_cap | global let g:[]u8; g.cap | 0
|
||||
* slice_ptr_null | global let g:[]u8; g.ptr==nil ? 7 : 0 | 7
|
||||
* str_len | global let s:str; s.len | 0
|
||||
* str_ptr_null | global let s:str; s.ptr==nil ? 9 : 0 | 9
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "slice_len",
|
||||
"package main;\n"
|
||||
"let g: []u8;\n"
|
||||
"export fn main() i32 = { return g.len: i32; };\n",
|
||||
0 },
|
||||
|
||||
{ "slice_cap",
|
||||
"package main;\n"
|
||||
"let g: []u8;\n"
|
||||
"export fn main() i32 = { return g.cap: i32; };\n",
|
||||
0 },
|
||||
|
||||
{ "slice_ptr_null",
|
||||
"package main;\n"
|
||||
"let g: []u8;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tif (g.ptr == nil) { return 7; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "str_len",
|
||||
"package main;\n"
|
||||
"let s: str;\n"
|
||||
"export fn main() i32 = { return s.len: i32; };\n",
|
||||
0 },
|
||||
|
||||
{ "str_ptr_null",
|
||||
"package main;\n"
|
||||
"let s: str;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tif (s.ptr == nil) { return 9; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ssgz_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/ssgz_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/ssgz_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
if (result == r->want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match.
|
||||
* This is the #10-part-b discriminator: the old size-keyed double-emit
|
||||
* produced a second DATAW row in the wwstage .s. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[96], cs[96], ws[96], cmd[1024];
|
||||
int rc = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ssgz_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/t.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(ws) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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, "slice_str_global_zero: 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,
|
||||
"slice_str_global_zero[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"slice_str_global_zero: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("slice_str_global_zero: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
* 687_slice_literal_global — module-level slice-literal static init
|
||||
* `let g: []T = [v0, v1, …];` materializes a writable backing + a 24B
|
||||
* { ptr, len, cap } header + a DATAR patching ptr -> backing, and cstage
|
||||
* / wwstage agree byte-for-byte and at runtime (task #10 part a).
|
||||
*
|
||||
* The bug: emit_lets / emitletdataw had str-lit + array-lit arms but no
|
||||
* slice-lit arm, so a `let g: []u8 = [1u8,2u8,3u8]` emitted NO `DATAW
|
||||
* main.g` — BOTH stages failed to link ("undefined reference to main.g").
|
||||
* byte-id-blind; only the link step exposed it. (wwstage further needed
|
||||
* #18: its checker desugared the module-level initializer to an N_SLICE
|
||||
* runtime borrow, so the rhs reached cgen as N_SLICE not N_ARRLIT — fixed
|
||||
* first so this arm sees the raw array literal, mirroring cstage.)
|
||||
*
|
||||
* The fix (BOTH stages, byte-identical): emit_slice_data / emitslicedata
|
||||
* route the k element bytes through the emit_array_lit_bytes choke-point
|
||||
* (synthesized [k]T), then emit the header (ptr placeholder + LE len + LE
|
||||
* cap, all = k) and the ptr-patch DATAR. Backing symbol "<mangled g>.d".
|
||||
*
|
||||
* Coverage: []u8 (element read-back + len + cap + sum), []i64 (wider
|
||||
* element), []i32, and a 1-element edge. Plus dual-stage asm byte-id.
|
||||
* Mutation-sanity: the old no-emit fails every row at LINK. Plus negative
|
||||
* rows where the slice-literal static init must FAIL the build loudly
|
||||
* (rule 7): read-only `def`, `...` repeat, and slice-of-str (per-element
|
||||
* relocs / #17 deferred).
|
||||
*
|
||||
* row | shape | want
|
||||
* ----------------+------------------------------------------+------
|
||||
* u8_e0 | let g:[]u8=[1,2,3]; g[0] | 1
|
||||
* u8_e2 | g[2] | 3
|
||||
* u8_len | g.len | 3
|
||||
* u8_cap | g.cap | 3
|
||||
* u8_sum | g[0]+g[1]+g[2]+g.len+g.cap | 12
|
||||
* i64_e1 | let g:[]i64=[10,20,30]; g[1] | 20
|
||||
* i64_lencap | g.len+g.cap | 6
|
||||
* i32_sum | let g:[]i32=[7,8,9]; g[0]+g[1]+g[2] | 24
|
||||
* one_edge | let g:[]u8=[42]; g[0]+g.len | 43
|
||||
* struct_pt | []pt=[pt{1,2},pt{3,4}]; sum+len+cap | 14
|
||||
* struct_3f | []q (u8,i64,i32) two elems; sum+len | 23
|
||||
* struct_one | []pt=[pt{5,6}]; sum+len+cap (count==1) | 13
|
||||
* struct_arrvar_l | local let arr:[2]pt; let g:[]pt=arr | 14
|
||||
*
|
||||
* #19: wwstage's checker over-rejected the inline NAMED-STRUCT-element
|
||||
* array->slice (`let g:[]pt=[pt{..},pt{..}]`) — its N_ARRLIT element-type
|
||||
* inference returned the N_STRUCTLIT body (N_TSTRUCT, per #66) and the
|
||||
* array->slice isassignable typeeqast saw a TNAME-vs-TSTRUCT kind mismatch
|
||||
* against the declared N_TNAME element. cstage already inferred the NAMED
|
||||
* type. Fix: infer the named type for a named struct-literal first element
|
||||
* (check.ww N_ARRLIT arm); typeeqast untouched.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "u8_e0",
|
||||
"package main;\n"
|
||||
"let g: []u8 = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = { return g[0]: i32; };\n",
|
||||
1 },
|
||||
{ "u8_e2",
|
||||
"package main;\n"
|
||||
"let g: []u8 = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = { return g[2]: i32; };\n",
|
||||
3 },
|
||||
{ "u8_len",
|
||||
"package main;\n"
|
||||
"let g: []u8 = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = { return g.len: i32; };\n",
|
||||
3 },
|
||||
{ "u8_cap",
|
||||
"package main;\n"
|
||||
"let g: []u8 = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = { return g.cap: i32; };\n",
|
||||
3 },
|
||||
{ "u8_sum",
|
||||
"package main;\n"
|
||||
"let g: []u8 = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn (g[0]:i32)+(g[1]:i32)+(g[2]:i32)+(g.len:i32)+(g.cap:i32);\n"
|
||||
"};\n",
|
||||
12 },
|
||||
{ "i64_e1",
|
||||
"package main;\n"
|
||||
"let g: []i64 = [10i64, 20i64, 30i64];\n"
|
||||
"export fn main() i32 = { return g[1]: i32; };\n",
|
||||
20 },
|
||||
{ "i64_lencap",
|
||||
"package main;\n"
|
||||
"let g: []i64 = [10i64, 20i64, 30i64];\n"
|
||||
"export fn main() i32 = { return (g.len:i32)+(g.cap:i32); };\n",
|
||||
6 },
|
||||
{ "i32_sum",
|
||||
"package main;\n"
|
||||
"let g: []i32 = [7i32, 8i32, 9i32];\n"
|
||||
"export fn main() i32 = { return (g[0]:i32)+(g[1]:i32)+(g[2]:i32); };\n",
|
||||
24 },
|
||||
{ "one_edge",
|
||||
"package main;\n"
|
||||
"let g: []u8 = [42u8];\n"
|
||||
"export fn main() i32 = { return (g[0]:i32)+(g.len:i32); };\n",
|
||||
43 },
|
||||
/* aliased slice type: `type S = []u8` IS a slice — both stages must
|
||||
* route through emit_slice_data. cstage resolves it via type_unwrap;
|
||||
* wwstage letvarisslice resolves the alias chain (#10). */
|
||||
{ "alias_slice",
|
||||
"package main;\n"
|
||||
"type S = []u8;\n"
|
||||
"let g: S = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = { return (g[0]:i32)+(g.len:i32)+(g.cap:i32); };\n",
|
||||
7 },
|
||||
/* #19: inline NAMED-STRUCT-element array->slice. wwstage's checker
|
||||
* over-rejected this — the N_ARRLIT element-type inference picked up
|
||||
* the N_STRUCTLIT's body (N_TSTRUCT, per #66) and the array->slice
|
||||
* isassignable typeeqast saw a TNAME-vs-TSTRUCT kind mismatch (the
|
||||
* declared element is N_TNAME). Fix infers the NAMED element. cstage
|
||||
* already accepted; emit_array_lit_bytes' struct-field recursion
|
||||
* makes the backing byte-identical. */
|
||||
{ "struct_pt",
|
||||
"package main;\n"
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"let g: []pt = [pt{a=1i64, b=2i64}, pt{a=3i64, b=4i64}];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn (g[0].a:i32)+(g[0].b:i32)+(g[1].a:i32)+(g[1].b:i32)+(g.len:i32)+(g.cap:i32);\n"
|
||||
"};\n",
|
||||
14 },
|
||||
/* 3-field mixed-width struct stresses field offsets in the backing
|
||||
* (u8 @0, i64 @8, i32 @16 — non-uniform strides). */
|
||||
{ "struct_3f",
|
||||
"package main;\n"
|
||||
"type q = struct { a: u8, b: i64, c: i32 };\n"
|
||||
"let g: []q = [q{a=1u8, b=2i64, c=3i32}, q{a=4u8, b=5i64, c=6i32}];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn (g[0].a:i32)+(g[0].b:i32)+(g[0].c:i32)+(g[1].a:i32)+(g[1].b:i32)+(g[1].c:i32)+(g.len:i32);\n"
|
||||
"};\n",
|
||||
23 },
|
||||
/* single-element []struct (count==1 backing edge). */
|
||||
{ "struct_one",
|
||||
"package main;\n"
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"let g: []pt = [pt{a=5i64, b=6i64}];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn (g[0].a:i32)+(g[0].b:i32)+(g.len:i32)+(g.cap:i32);\n"
|
||||
"};\n",
|
||||
13 },
|
||||
/* regression: the array-VARIABLE form already accepted (arr carries a
|
||||
* clean N_TNAME). Local scope, since the MODULE-scope variable form is
|
||||
* the still-deferred #22 (no backing symbol -> link fail on BOTH
|
||||
* stages). */
|
||||
{ "struct_arrvar_local",
|
||||
"package main;\n"
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet arr: [2]pt = [pt{a=1i64, b=2i64}, pt{a=3i64, b=4i64}];\n"
|
||||
"\tlet g: []pt = arr;\n"
|
||||
"\treturn (g[0].a:i32)+(g[0].b:i32)+(g[1].a:i32)+(g[1].b:i32)+(g.len:i32)+(g.cap:i32);\n"
|
||||
"};\n",
|
||||
14 },
|
||||
};
|
||||
|
||||
/* slice-literal static init that must FAIL the build loudly (rule 7). */
|
||||
static const char *neg[] = {
|
||||
/* read-only `def` can't carry the ptr reloc (DATAR holder must be
|
||||
* DATAW, w6a asm.c:362). */
|
||||
"package main;\n"
|
||||
"def g: []u8 = [1u8, 2u8, 3u8];\n"
|
||||
"export fn main() i32 = { return g.len: i32; };\n",
|
||||
/* `...` repeat has no target length in a slice literal. Uses the
|
||||
* reachable repeat spelling `[v...]` (no comma) so the cgen loud-stop
|
||||
* is exercised — the comma form `[v, ...]` parse-errors first and
|
||||
* would test the parser, not the emit_slice_data guard. */
|
||||
"package main;\n"
|
||||
"let g: []u8 = [1u8, 2u8...];\n"
|
||||
"export fn main() i32 = { return g.len: i32; };\n",
|
||||
/* slice-of-str needs per-element relocs (#17) — deferred loud-stop. */
|
||||
"package main;\n"
|
||||
"let g: []str = [\"a\", \"b\"];\n"
|
||||
"export fn main() i32 = { return g.len: i32; };\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, setupfail = 0, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/slg_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/slg_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/slg_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { setupfail = 1; goto cleanup; }
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) { setupfail = 1; goto cleanup; }
|
||||
|
||||
/* Ordinary build retains caller-owned .sepwork scratch beside -o. */
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
if (setupfail)
|
||||
fprintf(stderr, "row[%s]: temporary setup failed\n", r->label);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
if (result == r->want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* build_should_fail — 0 on the expected rejection, positive on an
|
||||
* unexpected successful build, negative on fixture infrastructure failure. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, setupfail = 0, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/slgn_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(s, sizeof s, "%s/slgn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/slgn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { setupfail = 1; goto cleanup; }
|
||||
int writefail = fputs(src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) { setupfail = 1; goto cleanup; }
|
||||
|
||||
/* A rejected ordinary build can also leave caller-owned .sepwork. */
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
result = rc == 0 ? 1 : 0; /* build must NOT succeed */
|
||||
cleanup:
|
||||
if (setupfail)
|
||||
fprintf(stderr, "slice_literal_global[neg%d]: temporary setup failed\n", i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(s) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "slice_literal_global[neg%d]: temporary cleanup failed\n", i);
|
||||
if (result == 0) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[96], cs[96], ws[96], cmd[1024];
|
||||
int rc = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/slg_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/t.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(ws) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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 nn = (int)(sizeof neg / sizeof neg[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, "slice_literal_global: 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,
|
||||
"slice_literal_global[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
int nr = build_should_fail(drivers[d].path, neg[i], 100 + i);
|
||||
if (nr != 0) {
|
||||
if (nr > 0)
|
||||
fprintf(stderr,
|
||||
"slice_literal_global[%s][neg%d]: built ok, "
|
||||
"expected a loud error\n",
|
||||
drivers[d].name, i);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"slice_literal_global[%s][neg%d]: fixture infrastructure failed\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"slice_literal_global: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("slice_literal_global: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* 688_global_arr_elem_field — a module-GLOBAL indexed struct-element
|
||||
* `.field` READ (`g[i].field`, g at module scope) lowers the field
|
||||
* offset, not just the element stride. The READ twin of #11 (which
|
||||
* fixed the global `g[i] = v` WRITE) and the #15 sibling.
|
||||
*
|
||||
* Pre-fix (#21): the `arr[i].field` N_DOT branch in both stages was
|
||||
* gated on a LOCAL lookup (cstage localfind != 0, wwstage
|
||||
* localfindnode != nil). A module-global base missed it and fell
|
||||
* through — cstage to a generic index-load that drops f->offset
|
||||
* (reads element[i] at offset 0, so g[0].b returned a's value), and
|
||||
* wwstage to the module-qualified fallback (garbage — no g load).
|
||||
* Byte-id-divergent silent miscompile.
|
||||
*
|
||||
* The fix (BOTH stages, converged byte-identical): resolve the global
|
||||
* the same way the N_INDEX arm does (cstage let_islet || def_isarraydef;
|
||||
* wwstage letvartnode || defvartnode) and dispatch the base load by
|
||||
* shape — array -> LEAQ name(SB) (the symbol IS the storage),
|
||||
* slice/ptr -> MOVQ name(SB) (the symbol's first word IS the .ptr) —
|
||||
* then load the field at f->offset, exactly as the local arm does.
|
||||
* esz = element stride and f->offset both come from the type table.
|
||||
*
|
||||
* Coverage:
|
||||
* arr_a0/a1 | [2]pt struct array, g[0].a / g[1].a — control,
|
||||
* | offset 0. RUNTIME + byte-id.
|
||||
* arr_b0/b1 | [2]pt, g[0].b / g[1].b — THE bug (offset 8, the
|
||||
* | reads that returned a's value pre-fix). RUNTIME.
|
||||
* rec_* | [2]rec { tag:u8, x:i32, y:i64 } — non-8-aligned
|
||||
* | fields (x@4, y@8) + a u8 sub-word leaf, to stress
|
||||
* | f->offset and the load-width path. RUNTIME.
|
||||
* slice_x1 | [2]rec backing + `let g: []rec = arr;`, g[1].x
|
||||
* | read. Exercises the slice-base MOVQ-deref arm
|
||||
* | (MOVQ g(SB),BX loads .ptr) — the asm is byte-id and
|
||||
* | correct, but a slice-of-struct module global does
|
||||
* | not data-emit a symbol yet (a separate, pre-existing
|
||||
* | data-emission gap, sibling of #10/#20), so it cannot
|
||||
* | LINK/run. Asserted byte-id ONLY; filed separately.
|
||||
*
|
||||
* Why this was never caught: 680/681 cover only LOCAL `[N]struct`
|
||||
* bases (BP-relative). The global base routes through name(SB) and was
|
||||
* a distinct, untested arm.
|
||||
*
|
||||
* Cstage and wwstage each on every runtime fixture; wwstage gated on
|
||||
* access(X_OK). All fixtures additionally asm-byte-id checked.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
/* want == BYTEID_ONLY: the row cannot LINK/run (a slice-of-struct
|
||||
* module global does not data-emit yet — separate gap), so only the
|
||||
* cstage/wwstage asm-byte-id is asserted, never an exit value. */
|
||||
#define BYTEID_ONLY (-2147483647 - 1)
|
||||
|
||||
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[] = {
|
||||
/* [2]pt, .a field at offset 0 — control. */
|
||||
{ "arr_a0",
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];\n"
|
||||
"export fn main() i32 = { return g[0].a: i32; };\n",
|
||||
1 },
|
||||
{ "arr_a1",
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];\n"
|
||||
"export fn main() i32 = { return g[1].a: i32; };\n",
|
||||
3 },
|
||||
/* [2]pt, .b at offset 8 — THE bug: pre-fix g[i].b returned a's
|
||||
* value (cstage) or garbage (wwstage). */
|
||||
{ "arr_b0",
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];\n"
|
||||
"export fn main() i32 = { return g[0].b: i32; };\n",
|
||||
2 },
|
||||
{ "arr_b1",
|
||||
"type pt = struct { a: i64, b: i64 };\n"
|
||||
"let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];\n"
|
||||
"export fn main() i32 = { return g[1].b: i32; };\n",
|
||||
4 },
|
||||
/* [2]rec, non-8-aligned i32 field (x@4) on the second element —
|
||||
* stresses f->offset combined with the element stride. */
|
||||
{ "rec_x1",
|
||||
"type rec = struct { tag: u8, x: i32, y: i64 };\n"
|
||||
"let g: [2]rec = [rec { tag = 1u8, x = 100, y = 11 },"
|
||||
" rec { tag = 2u8, x = 200, y = 22 }];\n"
|
||||
"export fn main() i32 = { return g[1].x: i32; };\n",
|
||||
200 },
|
||||
/* [2]rec, i64 field (y@8) on the second element. */
|
||||
{ "rec_y1",
|
||||
"type rec = struct { tag: u8, x: i32, y: i64 };\n"
|
||||
"let g: [2]rec = [rec { tag = 1u8, x = 100, y = 11 },"
|
||||
" rec { tag = 2u8, x = 200, y = 22 }];\n"
|
||||
"export fn main() i32 = { return g[1].y: i32; };\n",
|
||||
22 },
|
||||
/* [2]rec, u8 leaf (tag@0) — sub-word zero-extend load on a
|
||||
* global element base. */
|
||||
{ "rec_tag1",
|
||||
"type rec = struct { tag: u8, x: i32, y: i64 };\n"
|
||||
"let g: [2]rec = [rec { tag = 1u8, x = 100, y = 11 },"
|
||||
" rec { tag = 2u8, x = 200, y = 22 }];\n"
|
||||
"export fn main() i32 = { return g[1].tag: i32; };\n",
|
||||
2 },
|
||||
/* slice-base MOVQ-deref arm: `let g: []rec = arr;`, g[1].b read.
|
||||
* Byte-id only — a slice-of-struct module global does not
|
||||
* data-emit yet (separate gap), so it cannot link/run. The .s
|
||||
* holds MOVQ g(SB),BX (the .ptr load) + the correct f->offset on
|
||||
* both stages. */
|
||||
{ "slice_x1",
|
||||
"type rec = struct { tag: u8, x: i32, y: i64 };\n"
|
||||
"let arr: [2]rec = [rec { tag = 1u8, x = 100, y = 11 },"
|
||||
" rec { tag = 2u8, x = 200, y = 22 }];\n"
|
||||
"let g: []rec = arr;\n"
|
||||
"export fn main() i32 = { return g[1].x: i32; };\n",
|
||||
BYTEID_ONLY },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gae_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/gae_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/gae_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
wwtest_fputs(r->src, f);
|
||||
int writefail = ferror(f);
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
if (result == r->want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. Pins rule-10 convergence: pre-fix the global-base
|
||||
* read diverged (cstage dropped f->offset, wwstage emitted no g load). */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[96], cs[96], ws[96], cmd[1024];
|
||||
int rc = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gae_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/t.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
wwtest_fputs(r->src, f);
|
||||
int writefail = ferror(f);
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(ws) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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, "global_arr_elem_field: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].want == BYTEID_ONLY)
|
||||
continue; /* byte-id only below */
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"global_arr_elem_field[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"global_arr_elem_field: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("global_arr_elem_field: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* 689_globptr_field_read_run (#15) — reading a field through a module-
|
||||
* GLOBAL `*struct` pointer base (`gp.f`, single-dot) loads correctly AND
|
||||
* cstage / wwstage agree byte-for-byte.
|
||||
*
|
||||
* THE BUG (both stages, two different wrong ways — no byte-id oracle until
|
||||
* one side was fixed, but they DID diverge): a global `*struct` ptr base
|
||||
* carries via_ptr=1 but has NO local slot (localfind/localfindnode = 0),
|
||||
* so the pointer-to-struct field-READ arm never loaded the pointer VALUE
|
||||
* from the global's data slot before applying the field offset:
|
||||
* cstage : `MOVQ (BP),BX; MOVQ 8(BX),AX` — derefed the saved BP → SEGV.
|
||||
* wwstage: `MOVQ f(SB),AX` — collapsed gp.f to a bare
|
||||
* undefined symbol `f`.
|
||||
* Read twin of the #6 STORE fix (689_globptr_field_store_run).
|
||||
*
|
||||
* THE FIX (both stages converge): load the pointer value via SB first —
|
||||
* `MOVQ gp(SB),BX; MOVQ off(BX),AX`
|
||||
* cstage: base load is now `off==0 && let_islet → MOVQ mafn(gp)(SB),BX`.
|
||||
* wwstage: a dedicated global-ptr arm (lc==nil + isletvar + N_TPTR) emits
|
||||
* `MOVQ name(SB),BX` and shares the field tail (cgptrfieldload).
|
||||
*
|
||||
* shape | want
|
||||
* -------------------------------+------
|
||||
* gp.f (scalar, offset 8) | 170
|
||||
* gp.f+gp.g (scalar, offset 0/8) | 14
|
||||
* let x=gp.sf; x.len (str field) | 3
|
||||
* gp.d : i32 (f64 field) | 7
|
||||
* match gp.t (tagged field, 16B) | 5
|
||||
* gp.len (*[]u8 pseudo-field) | 3
|
||||
* lp.f (LOCAL ptr control) | 42
|
||||
* bump param p.f (param control) | 10
|
||||
* gp.sf.len (CHAINED str leaf) | 3 (#16)
|
||||
* gp.x.q (CHAINED struct leaf)| 7 (#16)
|
||||
* gp.x.q i64 sink, (v>>32) | 1 (#18 spine-narrow PIN)
|
||||
* Each row also asserts w6c .s == w6c_ww .s byte-identical.
|
||||
* Pre-fix: cstage SEGV(139) on every global-ptr row; wwstage ran wrong;
|
||||
* cs vs ww .s differed.
|
||||
*
|
||||
* #16 (chained-through-global-ptr-root, the last two rows): after #15
|
||||
* both stages RAN correct but byte-DIVERGED on the chained-N_DOT spine —
|
||||
* cstage offset-folds (LEAQ name(SB),CX; MOVQ (CX),CX; MOVQ <folded>(CX))
|
||||
* while wwstage's chained spine bailed (dotchainresolve missed a global
|
||||
* `*struct` root) onto the naive inner-load-and-shuffle catch-all. Fix
|
||||
* aligns wwstage UP: dotchainresolve resolves a global `*struct` root as
|
||||
* isglobal && ptrroot and the spine emits the same offset-fold (rule 10).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *name;
|
||||
const char *src;
|
||||
int want;
|
||||
};
|
||||
|
||||
static const struct row ROWS[] = {
|
||||
{ "scalar_off8",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, f: i64 };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,f=0}; gp = &s;"
|
||||
" s.f = 170; return gp.f: i32; };\n", 170 },
|
||||
{ "scalar_off0_off8",
|
||||
"package main;\n"
|
||||
"type S = struct { f: i64, g: i64 };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{f=0,g=0}; gp = &s;"
|
||||
" s.f = 5; s.g = 9; return (gp.f + gp.g): i32; };\n", 14 },
|
||||
{ "str_field",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, sf: str };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,sf=\"abc\"}; gp = &s;"
|
||||
" let x: str = gp.sf; return x.len: i32; };\n", 3 },
|
||||
{ "float_field",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, d: f64 };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,d=0.0}; gp = &s;"
|
||||
" s.d = 7.0; return (gp.d): i32; };\n", 7 },
|
||||
{ "tagged_field",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, t: (i64 | i32) };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,t=5i32}; gp = &s;"
|
||||
" let v: i32 = 0; match (gp.t) { case let x: i32 => v = x;"
|
||||
" case let y: i64 => v = y: i32; }; return v; };\n", 5 },
|
||||
{ "tagged_slice_field",
|
||||
/* #17: a 32B slice-payload tagged field (8B tag + 24B slice =
|
||||
* full 4-reg cursor) read via global *struct ptr. cgloadtaggedfield
|
||||
* fills AX/DX/CX/R8; wwstage loaded R8@+24 before CX@+16 while
|
||||
* cstage's direct *struct-ptr arm loads them in offset order, so the
|
||||
* .s byte-diverged (both ran correct). The 16B tagged_field row above
|
||||
* is too small to fire the R8/CX split. */
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, t: (i64 | []u8) };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,t=3i64}; gp = &s;"
|
||||
" let v: i32 = 0; match (gp.t) { case let x: i64 => v = x: i32;"
|
||||
" case let y: []u8 => v = len(y): i32; }; return v; };\n", 3 },
|
||||
{ "tagged_slice_field_local",
|
||||
/* #17 twin: same 32B slice-payload tagged read, but via a LOCAL
|
||||
* *struct ptr. cgptrfieldload is shared by the local (MOVQ off(BP),BX)
|
||||
* and global (MOVQ name(SB),BX) direct *struct-ptr arms, so the same
|
||||
* cxlast=false fix closes both — this row locks the local path against
|
||||
* a future regression of the R8/CX order. */
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, t: (i64 | []u8) };\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,t=3i64}; let p: *S = &s;"
|
||||
" let v: i32 = 0; match (p.t) { case let x: i64 => v = x: i32;"
|
||||
" case let y: []u8 => v = len(y): i32; }; return v; };\n", 3 },
|
||||
{ "pslice_len",
|
||||
"package main;\n"
|
||||
"let gp: *[]u8 = nil;\n"
|
||||
"export fn main() i32 = { let b: []u8 = [1u8,2u8,3u8]; gp = &b;"
|
||||
" return (gp.len): i32; };\n", 3 },
|
||||
{ "local_ptr_ctrl",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, f: i64 };\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,f=0}; let lp: *S = &s;"
|
||||
" s.f = 42; return lp.f: i32; };\n", 42 },
|
||||
{ "param_ctrl",
|
||||
"package main;\n"
|
||||
"type S = struct { f: i64, g: i64 };\n"
|
||||
"fn rd(p: *S) i32 = { return (p.f + p.g): i32; };\n"
|
||||
"export fn main() i32 = { let s: S = S{f=4,g=6}; return rd(&s); };\n", 10 },
|
||||
/* #16: CHAINED read through a global-`*struct` root. Pre-fix BOTH stages
|
||||
* ran correct after #15 but byte-DIVERGED — cstage offset-folds (LEAQ
|
||||
* name(SB),CX; MOVQ (CX),CX; MOVQ <folded>(CX)) while wwstage's chained
|
||||
* spine bailed (dotchainresolve missed a global `*struct` root) onto the
|
||||
* naive inner-load-and-shuffle catch-all. */
|
||||
{ "chain_str_len",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, sf: str };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,sf=\"abc\"}; gp = &s;"
|
||||
" return gp.sf.len: i32; };\n", 3 },
|
||||
{ "chain_struct_field",
|
||||
"package main;\n"
|
||||
"type Inner = struct { p: i64, q: i64 };\n"
|
||||
"type S = struct { a: i64, x: Inner };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,x=Inner{p=0,q=0}}; gp = &s;"
|
||||
" s.x.q = 7; return gp.x.q: i32; };\n", 7 },
|
||||
/* #18 PIN: a CHAINED read into an i64 SINK must NOT carry a spurious load/
|
||||
* spine narrow. q holds 0x1_0000_0001; we observe the HIGH word via
|
||||
* (v>>32):i32 — a truncating MOVSXD on the chained load would sign-extend
|
||||
* the low word (1) and the high word would vanish (1 -> 0). The legitimate
|
||||
* MOVSXD must stay on the `:i32` RETURN cast only (after a full 64-bit
|
||||
* SARQ over the complete i64). Verified no-bug at HEAD; this row guards the
|
||||
* spine-narrow family (cf. fld-load-op / int-cast-no-truncate fixes). */
|
||||
{ "chain_i64_sink",
|
||||
"package main;\n"
|
||||
"type T = struct { q: i64 };\n"
|
||||
"type S = struct { x: T };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{x=T{q=0}}; gp = &s;"
|
||||
" s.x.q = 4294967297; let v: i64 = gp.x.q; return (v >> 32): i32; };\n", 1 },
|
||||
};
|
||||
|
||||
#define NROWS ((int)(sizeof ROWS / sizeof ROWS[0]))
|
||||
|
||||
/* build+run one row through `driver`; return exit code or -1. */
|
||||
static int
|
||||
run_driver(const char *driver, const char *src, int idx, int want)
|
||||
{
|
||||
char tmpdir[64], srcf[128], outbin[160], scratch[192], rmcmd[224], cmd[2400];
|
||||
int result = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gpfr_%d_%d_d", getpid(), idx);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(srcf, sizeof srcf, "%s/t.ww", tmpdir);
|
||||
snprintf(outbin, sizeof outbin, "%s/t", tmpdir);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(srcf, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, srcf);
|
||||
if (runwait(cmd) != 0) goto cleanup;
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(srcf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "globptr_field_read[%d]: temporary cleanup failed\n", idx);
|
||||
if (result == want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Emit .s for `tool`; the caller owns srcf and asmf. */
|
||||
static int
|
||||
emit_asm(const char *bin, const char *tool, const char *srcf,
|
||||
const char *asmf)
|
||||
{
|
||||
char cmd[2400];
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -o %s %s 2>/dev/null",
|
||||
bin, tool, asmf, srcf);
|
||||
int rc = runwait(cmd);
|
||||
return rc == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
files_identical(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return 0; }
|
||||
int ca, cb, same = 1;
|
||||
do {
|
||||
ca = fgetc(fa); cb = fgetc(fb);
|
||||
if (ca != cb) { same = 0; break; }
|
||||
} while (ca != EOF);
|
||||
fclose(fa); fclose(fb);
|
||||
return same;
|
||||
}
|
||||
|
||||
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 fail = 0, total = 0;
|
||||
for (int i = 0; i < NROWS; i++) {
|
||||
const struct row *r = &ROWS[i];
|
||||
|
||||
total++;
|
||||
int gc = run_driver(cdrv, r->src, i, r->want);
|
||||
if (gc != r->want) {
|
||||
fprintf(stderr, "globptr_field_read[%s,cstage]: run=%d want=%d\n",
|
||||
r->name, gc, r->want);
|
||||
fail++;
|
||||
}
|
||||
if (have_ww) {
|
||||
total++;
|
||||
int gw = run_driver(wdrv, r->src, i, r->want);
|
||||
if (gw != r->want) {
|
||||
fprintf(stderr, "globptr_field_read[%s,wwstage]: run=%d want=%d\n",
|
||||
r->name, gw, r->want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* byte-id: w6c .s == w6c_ww .s */
|
||||
if (have_ww) {
|
||||
char td[80], srcf[96], csf[96], wwf[96];
|
||||
int ec = -1, ew = -1, same = 0, setupfail = 0, cleanfail = 0;
|
||||
snprintf(td, sizeof td, "/tmp/gpfr_asm_%d_%d", getpid(), i);
|
||||
snprintf(srcf, sizeof srcf, "%s/t.ww", td);
|
||||
snprintf(csf, sizeof csf, "%s/c.s", td);
|
||||
snprintf(wwf, sizeof wwf, "%s/w.s", td);
|
||||
total++;
|
||||
if (mkdir(td, 0755) == 0) {
|
||||
FILE *f = fopen(srcf, "wb");
|
||||
if (f) {
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (!writefail) {
|
||||
ec = emit_asm(bin, "w6c", srcf, csf);
|
||||
ew = emit_asm(bin, "w6c_ww", srcf, wwf);
|
||||
same = ec == 0 && ew == 0
|
||||
&& files_identical(csf, wwf);
|
||||
} else setupfail = 1;
|
||||
} else setupfail = 1;
|
||||
if (unlink(srcf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(csf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(wwf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(td) != 0) cleanfail = 1;
|
||||
} else setupfail = 1;
|
||||
if (setupfail)
|
||||
fprintf(stderr, "globptr_field_read[%s]: temporary setup failed\n",
|
||||
r->name);
|
||||
if (!same) {
|
||||
fprintf(stderr, "globptr_field_read[%s]: cs/ww .s NOT "
|
||||
"byte-identical (ec=%d ew=%d)\n", r->name, ec, ew);
|
||||
fail++;
|
||||
}
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "globptr_field_read[%s]: temporary cleanup failed\n",
|
||||
r->name);
|
||||
if (same) fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "globptr_field_read_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("globptr_field_read_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* 689_globptr_field_store_run (#6) — a module-GLOBAL `*struct` pointer base
|
||||
* (`gp.f = v` / `gp.f += v` / `gp.x.y = v` / `gp.a[i].f = v`) stores
|
||||
* correctly AND cstage / wwstage agree byte-for-byte.
|
||||
*
|
||||
* THE BUG (cstage only): a global `*struct` ptr base carries via_ptr=1 but
|
||||
* has no local slot — localfind returns 0, so the enumerated via_ptr field-
|
||||
* store arms emitted `MOVQ (BP),BX` (deref the saved BP = garbage → SEGV).
|
||||
* The plain-scalar-`=` STORE half was already correct (the #47/#8 fix, LEAQ
|
||||
* gp(SB)), so load/store were asymmetric. wwstage routes every case EXCEPT
|
||||
* plain-scalar-`=` through its F6 cgplaceaddr resolver and was correct.
|
||||
*
|
||||
* THE FIX (align cstage UP): decline the global-`*struct`-ptr base at the
|
||||
* enumerated arm's dispatch for compound + non-scalar (and a chained
|
||||
* `gp.x.y` root), so cstage falls to its OWN F6 assign-resolver — byte-
|
||||
* identical to wwstage. Plain-scalar-`=` stays in the enumerated arm (its
|
||||
* #47 byte-id is undisturbed); a local ptr and a param at offset 0
|
||||
* (localfind != 0) also stay local.
|
||||
*
|
||||
* shape | want
|
||||
* ------------------------------+------
|
||||
* gp.f += 5 (compound off 8) | 5
|
||||
* gp.f += 7 (compound off 0) | 7
|
||||
* gp.in = Inner{7,9} (struct) | 16
|
||||
* gp.sf = "hi" (str field) | 2
|
||||
* gp.x.y=11; gp.x.z+=7 (chain) | 18
|
||||
* gp.a[1].f=5; gp.a[1].g+=8 | 13
|
||||
* lp.f += 5 (LOCAL ptr ctrl) | 5
|
||||
* bump(&s): p.f/p.g (param ctrl)| 10
|
||||
* Each row also asserts w6c .s == w6c_ww .s byte-identical.
|
||||
* Pre-fix: cstage SEGV(139) on every global-ptr row; cs vs ww .s differed.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *name;
|
||||
const char *src;
|
||||
int want;
|
||||
};
|
||||
|
||||
static const struct row ROWS[] = {
|
||||
{ "compound_off8",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, f: i64 };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,f=0}; gp = &s;"
|
||||
" gp.f += 5; return s.f: i32; };\n", 5 },
|
||||
{ "compound_off0",
|
||||
"package main;\n"
|
||||
"type S = struct { f: i64, g: i64 };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{f=0,g=0}; gp = &s;"
|
||||
" gp.f += 7; return s.f: i32; };\n", 7 },
|
||||
{ "struct_field",
|
||||
"package main;\n"
|
||||
"type I = struct { x: i64, y: i64 };\n"
|
||||
"type S = struct { a: i64, in: I };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,in=I{x=0,y=0}}; gp = &s;"
|
||||
" gp.in = I{x=7,y=9}; return (s.in.x+s.in.y): i32; };\n", 16 },
|
||||
{ "str_field",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, sf: str };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,sf=\"\"}; gp = &s;"
|
||||
" gp.sf = \"hi\"; return (s.sf.len): i32; };\n", 2 },
|
||||
{ "chained",
|
||||
"package main;\n"
|
||||
"type I = struct { y: i64, z: i64 };\n"
|
||||
"type S = struct { a: i64, x: I };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,x=I{y=0,z=0}}; gp = &s;"
|
||||
" gp.x.y = 11; gp.x.z += 7; return (s.x.y+s.x.z): i32; };\n", 18 },
|
||||
{ "indexed",
|
||||
"package main;\n"
|
||||
"type E = struct { f: i64, g: i64 };\n"
|
||||
"type S = struct { n: i64, a: [3]E };\n"
|
||||
"let gp: *S = nil;\n"
|
||||
"export fn main() i32 = { let s: S; gp = &s;"
|
||||
" gp.a[1].f = 5; gp.a[1].g += 8; return (s.a[1].f+s.a[1].g): i32; };\n", 13 },
|
||||
{ "local_ptr_ctrl",
|
||||
"package main;\n"
|
||||
"type S = struct { a: i64, f: i64 };\n"
|
||||
"export fn main() i32 = { let s: S = S{a=0,f=0}; let lp: *S = &s;"
|
||||
" lp.f += 5; return s.f: i32; };\n", 5 },
|
||||
{ "param_off0_ctrl",
|
||||
"package main;\n"
|
||||
"type S = struct { f: i64, g: i64 };\n"
|
||||
"fn bump(p: *S) void = { p.f += 3; p.g += 4; };\n"
|
||||
"export fn main() i32 = { let s: S = S{f=1,g=2}; bump(&s);"
|
||||
" return (s.f+s.g): i32; };\n", 10 },
|
||||
};
|
||||
|
||||
#define NROWS ((int)(sizeof ROWS / sizeof ROWS[0]))
|
||||
|
||||
/* build+run one row through `driver`; return exit code or -1. */
|
||||
static int
|
||||
run_driver(const char *driver, const char *src, int idx, int want)
|
||||
{
|
||||
char tmpdir[64], srcf[128], outbin[160], scratch[192], rmcmd[224], cmd[2400];
|
||||
int result = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gpfs_%d_%d_d", getpid(), idx);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(srcf, sizeof srcf, "%s/t.ww", tmpdir);
|
||||
snprintf(outbin, sizeof outbin, "%s/t", tmpdir);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(srcf, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, srcf);
|
||||
if (runwait(cmd) != 0) goto cleanup;
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(srcf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "globptr_field_store[%d]: temporary cleanup failed\n", idx);
|
||||
if (result == want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Emit .s for `tool`; the caller owns srcf and asmf. */
|
||||
static int
|
||||
emit_asm(const char *bin, const char *tool, const char *srcf,
|
||||
const char *asmf)
|
||||
{
|
||||
char cmd[2400];
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -o %s %s 2>/dev/null",
|
||||
bin, tool, asmf, srcf);
|
||||
int rc = runwait(cmd);
|
||||
return rc == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
files_identical(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return 0; }
|
||||
int ca, cb, same = 1;
|
||||
do {
|
||||
ca = fgetc(fa); cb = fgetc(fb);
|
||||
if (ca != cb) { same = 0; break; }
|
||||
} while (ca != EOF);
|
||||
fclose(fa); fclose(fb);
|
||||
return same;
|
||||
}
|
||||
|
||||
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 fail = 0, total = 0;
|
||||
for (int i = 0; i < NROWS; i++) {
|
||||
const struct row *r = &ROWS[i];
|
||||
|
||||
total++;
|
||||
int gc = run_driver(cdrv, r->src, i, r->want);
|
||||
if (gc != r->want) {
|
||||
fprintf(stderr, "globptr_field_store[%s,cstage]: run=%d want=%d\n",
|
||||
r->name, gc, r->want);
|
||||
fail++;
|
||||
}
|
||||
if (have_ww) {
|
||||
total++;
|
||||
int gw = run_driver(wdrv, r->src, i, r->want);
|
||||
if (gw != r->want) {
|
||||
fprintf(stderr, "globptr_field_store[%s,wwstage]: run=%d want=%d\n",
|
||||
r->name, gw, r->want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* byte-id: w6c .s == w6c_ww .s */
|
||||
if (have_ww) {
|
||||
char td[80], srcf[96], csf[96], wwf[96];
|
||||
int ec = -1, ew = -1, same = 0, setupfail = 0, cleanfail = 0;
|
||||
snprintf(td, sizeof td, "/tmp/gpfs_asm_%d_%d", getpid(), i);
|
||||
snprintf(srcf, sizeof srcf, "%s/t.ww", td);
|
||||
snprintf(csf, sizeof csf, "%s/c.s", td);
|
||||
snprintf(wwf, sizeof wwf, "%s/w.s", td);
|
||||
total++;
|
||||
if (mkdir(td, 0755) == 0) {
|
||||
FILE *f = fopen(srcf, "wb");
|
||||
if (f) {
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (!writefail) {
|
||||
ec = emit_asm(bin, "w6c", srcf, csf);
|
||||
ew = emit_asm(bin, "w6c_ww", srcf, wwf);
|
||||
same = ec == 0 && ew == 0
|
||||
&& files_identical(csf, wwf);
|
||||
} else setupfail = 1;
|
||||
} else setupfail = 1;
|
||||
if (unlink(srcf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(csf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(wwf) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(td) != 0) cleanfail = 1;
|
||||
} else setupfail = 1;
|
||||
if (setupfail)
|
||||
fprintf(stderr, "globptr_field_store[%s]: temporary setup failed\n",
|
||||
r->name);
|
||||
if (!same) {
|
||||
fprintf(stderr, "globptr_field_store[%s]: cs/ww .s NOT "
|
||||
"byte-identical (ec=%d ew=%d)\n", r->name, ec, ew);
|
||||
fail++;
|
||||
}
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "globptr_field_store[%s]: temporary cleanup failed\n",
|
||||
r->name);
|
||||
if (same) fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "globptr_field_store_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("globptr_field_store_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
/*
|
||||
* 689_structlit_slice_field — cstage and wwstage agree, byte-for-byte
|
||||
* and at runtime, that a struct-LITERAL initializer `cl{ items = b, n =
|
||||
* .. }` with a SLICE field copies the FULL 24B { ptr, len, cap } header
|
||||
* of that field (task #24).
|
||||
*
|
||||
* The bug: cg_structlit_fill / cgstructlitfill had a TY_STR arm that
|
||||
* stored all three header words (ptr@+0, len@+8, cap@+16), but NO
|
||||
* TY_SLICE arm — a slice field fell through to the generic scalar tail
|
||||
* which stored only the ptr word, so the stored .len and .cap read 0.
|
||||
* str fields (same 24B shape) worked; slice fields silently dropped
|
||||
* two words. Both stages emitted IDENTICAL wrong asm (the 990-997
|
||||
* byte-id gate was green on both-wrong), so RUNTIME readback is the
|
||||
* correctness net. Same is_str/is_slice discrimination gap as #10
|
||||
* part-b, here in the struct-literal field-init path.
|
||||
*
|
||||
* The fix (BOTH stages, converged byte-identical): the str arm's guard
|
||||
* widens to `TY_STR || TY_SLICE` (cstage) / `isstrtype || isslicetype`
|
||||
* (wwstage) — a slice rides the existing, already-correct 3-word store.
|
||||
* The TAGGED arm stays ordered BEFORE it, so a nullable/tagged slice
|
||||
* (TY_TAGGED) still routes to the widener, not the 3-word store.
|
||||
*
|
||||
* Mutation-sanity: the .len and .cap rows fail if the store drops
|
||||
* either word (the pre-fix 1-word slice store), and the scalar-field
|
||||
* rows (y.n, y.head) pin that the field beside/before the slice is
|
||||
* untouched. The str_* rows are a regression pin on the untouched str
|
||||
* arm.
|
||||
*
|
||||
* row | shape | want
|
||||
* ---------------+------------------------------------------+------
|
||||
* slice_len | cl{items=b,n=9}; b.len=5; y.items.len | 5
|
||||
* slice_cap | cl{items=b,n=9}; b.cap=8; y.items.cap | 8
|
||||
* slice_n | cl{items=b,n=9}; y.n (scalar beside slice)| 9
|
||||
* slice_ptr | hb[0]=4; cl{items=b,..}; y.items[0] | 4
|
||||
* off_len | struct{head:i64,s:[]u8}; s @foff 8; | 7
|
||||
* | s.len=7; y.s.len (pins disp+foff+8 hdr) |
|
||||
* off_cap | same; s.cap=8; y.s.cap (disp+foff+16) | 8
|
||||
* off_head | same; head=1; y.head (scalar before slice)| 1
|
||||
* two_b_len | struct{a:[]u8,b:[]u8}; b @foff 24; | 6
|
||||
* | b.len=6; y.b.len (2nd slice, non-0 foff) |
|
||||
* two_b_cap | same; b.cap=8; y.b.cap | 8
|
||||
* str_name_len | struct{s:[]u8,name:str}; name="hello"; | 5
|
||||
* | y.name.len (str arm regression pin) |
|
||||
* str_s_len | same; s.len=3; y.s.len (slice beside str) | 3
|
||||
*
|
||||
* The asm-byte-id rows pin the symmetric fix (cstage == wwstage); a
|
||||
* non-empty diff means one stage missed the new slice arm.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "slice_len",
|
||||
"package main;\n"
|
||||
"type cl = struct { items: []u8, n: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 5; b.cap = 8;\n"
|
||||
"\tlet y: cl = cl { items = b, n = 9i64 };\n"
|
||||
"\treturn y.items.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
{ "slice_cap",
|
||||
"package main;\n"
|
||||
"type cl = struct { items: []u8, n: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 5; b.cap = 8;\n"
|
||||
"\tlet y: cl = cl { items = b, n = 9i64 };\n"
|
||||
"\treturn y.items.cap: i32;\n"
|
||||
"};\n",
|
||||
8 },
|
||||
|
||||
{ "slice_n",
|
||||
"package main;\n"
|
||||
"type cl = struct { items: []u8, n: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 5; b.cap = 8;\n"
|
||||
"\tlet y: cl = cl { items = b, n = 9i64 };\n"
|
||||
"\treturn y.n: i32;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
|
||||
{ "slice_ptr",
|
||||
"package main;\n"
|
||||
"type cl = struct { items: []u8, n: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8; hb[0] = 4u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 5; b.cap = 8;\n"
|
||||
"\tlet y: cl = cl { items = b, n = 9i64 };\n"
|
||||
"\treturn y.items[0]: i32;\n"
|
||||
"};\n",
|
||||
4 },
|
||||
|
||||
/* slice field at a NON-ZERO field offset (head: i64 @0, s @8).
|
||||
* Pins disp+foff+8/+16 in the stored header. */
|
||||
{ "off_len",
|
||||
"package main;\n"
|
||||
"type hs = struct { head: i64, s: []u8 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 7; b.cap = 8;\n"
|
||||
"\tlet y: hs = hs { head = 1i64, s = b };\n"
|
||||
"\treturn y.s.len: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "off_cap",
|
||||
"package main;\n"
|
||||
"type hs = struct { head: i64, s: []u8 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 7; b.cap = 8;\n"
|
||||
"\tlet y: hs = hs { head = 1i64, s = b };\n"
|
||||
"\treturn y.s.cap: i32;\n"
|
||||
"};\n",
|
||||
8 },
|
||||
|
||||
{ "off_head",
|
||||
"package main;\n"
|
||||
"type hs = struct { head: i64, s: []u8 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 7; b.cap = 8;\n"
|
||||
"\tlet y: hs = hs { head = 1i64, s = b };\n"
|
||||
"\treturn y.head: i32;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
|
||||
/* two slice fields: a @0, b @24. Pins the 2nd slice's header at a
|
||||
* non-zero field offset (the 24B stride). */
|
||||
{ "two_b_len",
|
||||
"package main;\n"
|
||||
"type tt = struct { a: []u8, b: []u8 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet p: []u8; p.ptr = &hb[0]; p.len = 3; p.cap = 8;\n"
|
||||
"\tlet q: []u8; q.ptr = &hb[0]; q.len = 6; q.cap = 8;\n"
|
||||
"\tlet y: tt = tt { a = p, b = q };\n"
|
||||
"\treturn y.b.len: i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
|
||||
{ "two_b_cap",
|
||||
"package main;\n"
|
||||
"type tt = struct { a: []u8, b: []u8 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet p: []u8; p.ptr = &hb[0]; p.len = 3; p.cap = 8;\n"
|
||||
"\tlet q: []u8; q.ptr = &hb[0]; q.len = 6; q.cap = 8;\n"
|
||||
"\tlet y: tt = tt { a = p, b = q };\n"
|
||||
"\treturn y.b.cap: i32;\n"
|
||||
"};\n",
|
||||
8 },
|
||||
|
||||
/* str field beside a slice field — the untouched str arm must still
|
||||
* store its full header (regression pin). */
|
||||
{ "str_name_len",
|
||||
"package main;\n"
|
||||
"type sn = struct { s: []u8, name: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 3; b.cap = 8;\n"
|
||||
"\tlet y: sn = sn { s = b, name = \"hello\" };\n"
|
||||
"\treturn y.name.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
{ "str_s_len",
|
||||
"package main;\n"
|
||||
"type sn = struct { s: []u8, name: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 3; b.cap = 8;\n"
|
||||
"\tlet y: sn = sn { s = b, name = \"hello\" };\n"
|
||||
"\treturn y.s.len: i32;\n"
|
||||
"};\n",
|
||||
3 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], scratch[160], rmcmd[192], cmd[1024];
|
||||
int result = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/slf_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/slf_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/slf_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", outbin);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
result = runwait(outbin);
|
||||
cleanup:
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf -- %s", scratch);
|
||||
if (runwait(rmcmd) != 0) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
if (result == r->want) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match.
|
||||
* Pre-fix BOTH stages dropped len+cap identically (byte-id green on
|
||||
* both-wrong); the symmetric fix keeps them byte-identical and correct. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[96], cs[96], ws[96], cmd[1024];
|
||||
int rc = -1, cleanfail = 0;
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/slf_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
snprintf(src, sizeof src, "%s/t.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/c.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/w.s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
int writefail = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
cleanup:
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(cs) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(ws) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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, "structlit_slice_field: 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,
|
||||
"structlit_slice_field[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"structlit_slice_field: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("structlit_slice_field: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,500 +0,0 @@
|
||||
/*
|
||||
* 698_cgreturn_struct — whole-struct return ABI across both 3-reg
|
||||
* register-return (≤24B) and SysV sret (>24B, task #23) paths.
|
||||
*
|
||||
* Pre-#7: `return s;` from a struct-returning fn fell through to the
|
||||
* scalar path: only the first 8 bytes of the struct made it to AX,
|
||||
* the rest was silently dropped. #7 wired the ≤24B producer side
|
||||
* (materialise into @retscr, load AX/DX/CX). The >24B case stayed
|
||||
* OUT OF SCOPE — fell through to the same scalar path — until task
|
||||
* #23 landed standard SysV sret discipline (caller pre-allocates
|
||||
* dest, passes &dest in RDI, callee writes through *RDI and returns
|
||||
* RDI in RAX). Surfaced by lib/encoding/utf8 pre-flight when the
|
||||
* Hoehrmann decoder (`struct { offs: size, src: []u8 }`, 32B) hit
|
||||
* the OUT-OF-SCOPE path; the marker pattern bit precisely when its
|
||||
* scope hit, which is the right escalation signal.
|
||||
*
|
||||
* What this test pins:
|
||||
* - cstage and wwstage emit byte-identical asm for every fixture,
|
||||
* including the new 25B+ sret rows (the bootstrap byte-identity
|
||||
* invariant — if either stage's scanlocals / cgreturn / cgcall
|
||||
* drifts, the diff catches it).
|
||||
* - Each fixture compiles+links+runs without crashing under both
|
||||
* drivers (proves the prologue SUBQ reserves enough frame for
|
||||
* @retscr / @sretarg / @sretscr; an under-booked frame would
|
||||
* smash the saved BP / return address).
|
||||
* - End-to-end value verification (caller reads AX/DX/CX into a
|
||||
* dst slot, or sret writes through *RDI) is deferred to #5's
|
||||
* test surface and the new 9xx semantic test for #23. Here we
|
||||
* check the cgreturn side doesn't crash or produce invalid asm.
|
||||
*
|
||||
* Coverage: ≤24B shapes — 8B one-field, 16B two-i64, 24B three-i64,
|
||||
* mixed-alignment i32+i32+i64+i64 (totsize 24 with the i32-pair
|
||||
* packed), and N_IDENT rhs (let-init then `return p;`). Plus the
|
||||
* new 25B+ rows added with #23: 32B i64×4, the utf8 decoder shape
|
||||
* (i64 + []u8 = 32B aligned, the canonical surfacing case), and
|
||||
* 40B i64×5.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 8B single-i64 field: smallest struct return. Exercises the
|
||||
* sz=8 path where only AX is meaningful (DX/CX zero-padded). */
|
||||
{ "one_i64_lit",
|
||||
"type one = struct { v: i64 };\n"
|
||||
"fn mk() one = { return one { v = 42i64 }; };\n"
|
||||
"fn main() i32 = { mk(); return 0; };\n",
|
||||
0 },
|
||||
/* 16B two-i64 (the SysV 16B aggregate shape, but routed through
|
||||
* our 3-reg path uniformly). N_STRUCTLIT rhs. */
|
||||
{ "pair_i64_lit",
|
||||
"type pair = struct { a: i64, b: i64 };\n"
|
||||
"fn mk() pair = { return pair { a = 1i64, b = 2i64 }; };\n"
|
||||
"fn main() i32 = { mk(); return 0; };\n",
|
||||
0 },
|
||||
/* 24B three-i64 — the headline shape: AX/DX/CX each carry one
|
||||
* word, no zero pad needed in scratch. */
|
||||
{ "trip_i64_lit",
|
||||
"type trip = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"fn mk() trip = { return trip { x = 7i64, y = 11i64, z = 13i64 }; };\n"
|
||||
"fn main() i32 = { mk(); return 0; };\n",
|
||||
0 },
|
||||
/* Mixed i32+i32+i64+i64: registerstruct packs two i32s into
|
||||
* one quadword (a@0, b@4, c@8, d@16), totsize 24 after tail
|
||||
* pad. Exercises the MOVL store-op branch in the structlit
|
||||
* field walker. */
|
||||
{ "mix_i32_i32_i64_i64_lit",
|
||||
"type mix = struct { a: i32, b: i32, c: i64, d: i64 };\n"
|
||||
"fn mk() mix = { return mix { a = 3, b = 5, c = 11i64, d = 12i64 }; };\n"
|
||||
"fn main() i32 = { mk(); return 0; };\n",
|
||||
0 },
|
||||
/* N_IDENT rhs: let-init the struct then return it. Exercises
|
||||
* the word-copy-from-slot branch (vs the structlit branch
|
||||
* above). The let-init zero-fills the slot, so the trailing
|
||||
* partial-word handlers (MOVL/MOVB) stay dormant — but the
|
||||
* sz=24 path covers all three MOVQ words. */
|
||||
{ "trip_i64_ident",
|
||||
"type trip = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"fn mk() trip = {\n"
|
||||
" let p: trip = trip { x = 100i64, y = 200i64, z = 300i64 };\n"
|
||||
" return p;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = { mk(); return 0; };\n",
|
||||
0 },
|
||||
/* 32B four-i64 — first row past the 24B → sret threshold.
|
||||
* Hits the sret prologue (@sretarg ← DI), the structlit-fill
|
||||
* through *(@sretarg), and `MOVQ @sretarg(BP), AX; RET`. */
|
||||
{ "quad_i64_lit_sret",
|
||||
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
||||
"fn mk() quad = { return quad { a = 1i64, b = 2i64, c = 3i64, d = 4i64 }; };\n"
|
||||
"fn main() i32 = { let q: quad = mk(); return 0; };\n",
|
||||
0 },
|
||||
/* utf8 decoder shape (the surfacing case for #23):
|
||||
* `struct { offs: i64, src: []u8 }` — i64 at +0, slice at +8
|
||||
* (16B+8B alignment, totsize 32). N_IDENT rhs exercises the
|
||||
* sret word-copy-from-rhs-slot branch. */
|
||||
{ "decoder_ident_sret",
|
||||
"type decoder = struct { offs: i64, src: []u8 };\n"
|
||||
"fn mk(s: []u8) decoder = {\n"
|
||||
" let r: decoder;\n"
|
||||
" r.offs = 0i64;\n"
|
||||
" r.src = s;\n"
|
||||
" return r;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: [1]u8;\n"
|
||||
" let d: decoder = mk(b[0:1]);\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* 40B five-i64 — second sret row, sized past 32B to exercise
|
||||
* the sretscr / @sretarg sizing for a larger struct in the
|
||||
* same fn family. */
|
||||
{ "five_i64_lit_sret",
|
||||
"type five = struct { a: i64, b: i64, c: i64, d: i64, e: i64 };\n"
|
||||
"fn mk() five = {\n"
|
||||
" return five { a = 1i64, b = 2i64, c = 3i64, d = 4i64, e = 5i64 };\n"
|
||||
"};\n"
|
||||
"fn main() i32 = { let f: five = mk(); return 0; };\n",
|
||||
0 },
|
||||
/* #169 RETURN, maxalign<8: struct{i32,i32,i32} natural extent 12,
|
||||
* maxalign 4 → cstage rt->size = round(12,4) = 12. The N_IDENT
|
||||
* return word-copy loop sizes on rsz; pre-fix wwstage used the
|
||||
* slot-padded totsize (round(12,8) = 16) → emitted MOVQ+MOVQ
|
||||
* where cstage emits MOVQ+MOVL. Byte-id catches it; value
|
||||
* readback (a+b+c) is correct in both stages either way. */
|
||||
{ "trip_i32_ident_return",
|
||||
"type t3 = struct { a: i32, b: i32, c: i32 };\n"
|
||||
"fn mk() t3 = {\n"
|
||||
" let s: t3 = t3 { a = 1, b = 2, c = 3 };\n"
|
||||
" return s;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = { let p: t3 = mk(); return p.a + p.b + p.c - 6; };\n",
|
||||
0 },
|
||||
/* #169 RECV, maxalign==8 + sub-8 tail: struct{i64,i32} natural
|
||||
* extent 12, maxalign 8 → cstage lu->size = round(12,8) = 16.
|
||||
* The `let p = mk()` receive sizes on structnaturalsize; pre-fix
|
||||
* it returned the unrounded 12 → emitted MOVQ+MOVL where cstage
|
||||
* emits MOVQ+MOVQ. mk returns via structlit (no RETURN-side
|
||||
* divergence), isolating the RECV split. */
|
||||
{ "pair_i64_i32_recv",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn main() i32 = { let p: pt = mk(); return p.b - 9; };\n",
|
||||
0 },
|
||||
/* #169 RECV, composite [N]u8 field: struct{i32,[6]u8} natural
|
||||
* extent 10, maxalign 4 → cstage lu->size = round(10,4) = 12.
|
||||
* Guards that a [N]u8 field's natural extent (10, NOT slot-
|
||||
* rounded) feeds structabisize and rounds to maxalign, not 8:
|
||||
* pre-fix the RECV used the unrounded structnaturalsize (10) →
|
||||
* MOVW tail; the slot-padded totsize (16) would give MOVQ; only
|
||||
* the maxalign-rounded 12 gives the MOVL cstage emits. The [6]u8
|
||||
* align must come from tinfo.align (1), not an fsz ladder (which
|
||||
* would read 4 off the 6-byte slot) — here the i32 sets maxalign
|
||||
* anyway, but the row pins the composite-field path byte-id.
|
||||
* mk returns via struct literal so the bare-let local zero-init
|
||||
* (a separate same-class latent) stays out of the fixture. */
|
||||
{ "recv_i32_arr6_u8",
|
||||
"type s6 = struct { n: i32, a: [6]u8 };\n"
|
||||
"fn mk() s6 = "
|
||||
"{ return s6 { n = 4i32, a = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8] }; };\n"
|
||||
"fn main() i32 = { let p: s6 = mk(); return p.n - 4; };\n",
|
||||
0 },
|
||||
/* DOT register-RECV: `obj.f = mk();` where the field type is a
|
||||
* struct{i64,i32} (natural 12, maxalign 8 → cstage lu->size 16).
|
||||
* Pre-fix wwstage sized the AX/DX/CX → field stores on the
|
||||
* unrounded structnaturalsize (12) → MOVQ+MOVL where cstage emits
|
||||
* MOVQ+MOVQ. The byte-id discriminator hits regardless of value
|
||||
* read-back. Three flavors cover the *struct base, direct local,
|
||||
* and global base sites that share the class. */
|
||||
{ "dot_recv_call_local",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"type box = struct { v: pt };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box;\n"
|
||||
" b.v = mk();\n"
|
||||
" return b.v.b - 9;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
{ "dot_recv_call_viaptr",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"type box = struct { v: pt };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn fill(p: *box) void = { p.v = mk(); return; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box;\n"
|
||||
" fill(&b);\n"
|
||||
" return b.v.b - 9;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
{ "dot_recv_call_global",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"type box = struct { v: pt };\n"
|
||||
"let g: box = box { v = pt { a = 0i64, b = 0i32 } };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn main() i32 = { g.v = mk(); return g.v.b - 9; };\n",
|
||||
0 },
|
||||
/* structlit ELLIPSIS zero-fill at a DOT site. cstage zeroes the
|
||||
* slot at `lu->size` (maxalign-rounded); wwstage pre-fix passed
|
||||
* structnaturalsize (12 for struct{i64,i32}) as the totsize
|
||||
* bound but cstage rounds to maxalign (16) before zeroing. The
|
||||
* `..., ...` literal forces the ELLIPSIS path. Outer box keeps
|
||||
* maxalign==8 so its own zero-init / structlit-init stays out
|
||||
* of the registerstruct fsz-ladder latent (task #18). */
|
||||
{ "dot_structlit_ellipsis",
|
||||
"type s = struct { a: i64, b: i32 };\n"
|
||||
"type box = struct { v: s };\n"
|
||||
"fn mk() box = "
|
||||
"{ return box { v = s { a = 0i64, b = 0i32 } }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box = mk();\n"
|
||||
" b.v = s { a = 5i64, ... };\n"
|
||||
" return b.v.b;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Bare `let z: T;` zero-init of a maxalign<8 struct. Slot is
|
||||
* round-to-8 (ti.slotsize); cstage zeroes only `lu->size`
|
||||
* (maxalign-rounded, check.c:760). struct{i32,i32,i32} natural 12,
|
||||
* maxalign 4 → cstage emits XORQ + MOVQ + MOVL (12B); pre-fix
|
||||
* wwstage emitted XORQ + MOVQ + MOVQ (slot 16B). Value-correct
|
||||
* either way; byte-id catches the trailing 4B widen. */
|
||||
{ "bare_let_struct_maxalign4",
|
||||
"type t3 = struct { a: i32, b: i32, c: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let z: t3;\n"
|
||||
" return z.a + z.b + z.c;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* `let p2: T = p1;` ident memcpy. struct{i64,i32} natural 12,
|
||||
* maxalign 8 → cstage N_LET sets sz = lu->size = 16 (cgen.c:7533,
|
||||
* struct-IDENT branch :7869) and emits MOVQ+MOVQ for the per-qword
|
||||
* copy. Pre-fix wwstage sized the run on structnaturalsize (12) →
|
||||
* MOVQ+MOVL. The byte-id catches the 4B widen; both stages
|
||||
* round-trip the value correctly because the high 4B of the rhs
|
||||
* slot were zero-init'd by the source let. */
|
||||
{ "let_ident_memcpy_pair_i64_i32",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let p1: pt = pt { a = 7i64, b = 9i32 };\n"
|
||||
" let p2: pt = p1;\n"
|
||||
" return p2.b - 9;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* `s = mk();` ident-lhs register RECV (sibling of dot_recv_call_*).
|
||||
* struct{i64,i32} natural 12, maxalign 8 → cstage N_ASSIGN-IDENT
|
||||
* sets sz = lu->size = 16 (cgen.c:4704) and stores AX/DX with
|
||||
* MOVQ+MOVQ. Pre-fix wwstage used structnaturalsize (12) →
|
||||
* MOVQ+MOVL. byte-id discriminator regardless of value readback. */
|
||||
{ "assign_ident_call_recv_pair_i64_i32",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: pt = pt { a = 0i64, b = 0i32 };\n"
|
||||
" s = mk();\n"
|
||||
" return s.b - 9;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Nested struct N_CALL inside a structlit field: the outer literal
|
||||
* has field `v` whose type is struct{i64,i32}; the value is mk().
|
||||
* cstage's cg_structlit_fill reads fl->type->size at the nested-
|
||||
* call branch (cgen.c:2121) — per check.c:760 that's the maxalign-
|
||||
* rounded ABI extent (16). Pre-fix wwstage cgstructlitfill used
|
||||
* structnaturalsize (12) → MOVQ+MOVL where cstage emits MOVQ+MOVQ.
|
||||
* mk returns via a structlit so the producer side stays out of the
|
||||
* comparison; only the nested-call-recv ladder shifts. */
|
||||
{ "structlit_nested_call_recv_pair_i64_i32",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"type box = struct { v: pt };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box = box { v = mk() };\n"
|
||||
" return b.v.b - 9;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], sepwork[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcrs_%d_d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
fprintf(stderr, "row[%s]: mkdir %s failed\n", r->label, tmpdir);
|
||||
return -1;
|
||||
}
|
||||
snprintf(src, sizeof src, "%s/wcrs_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wcrs_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin);
|
||||
|
||||
int got = -1;
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
wwtest_fputs(r->src, f);
|
||||
int writebad = ferror(f);
|
||||
if (fclose(f) != 0) writebad = 1;
|
||||
if (writebad) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
got = runwait(outbin);
|
||||
|
||||
/* Ordinary build leaves `<output>.sepwork` to its caller. This exact
|
||||
* tree is beneath tmpdir, whose mkdir above established ownership. */
|
||||
cleanup:
|
||||
{
|
||||
int cleanup_fail = 0;
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", sepwork);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n",
|
||||
r->label, sepwork);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (unlink(src) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n", r->label, src);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n",
|
||||
r->label, outbin);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n",
|
||||
r->label, tmpdir);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (cleanup_fail && got == r->want) got = -1;
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's w6c_ww
|
||||
* and diff. Catches scanlocals / cgreturn drift between the two stages,
|
||||
* which the bootstrap byte-identity (995_self_rebuild) covers globally
|
||||
* but doesn't surface as a focused-fixture failure. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], cs[128], ws[128], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcrs_asm_%d_%d", getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
fprintf(stderr, "row[%s]: mkdir %s failed\n", r->label, tmpdir);
|
||||
return -1;
|
||||
}
|
||||
snprintf(src, sizeof src, "%s/input.ww", tmpdir);
|
||||
snprintf(cs, sizeof cs, "%s/cstage.s", tmpdir);
|
||||
snprintf(ws, sizeof ws, "%s/wwstage.s", tmpdir);
|
||||
|
||||
int rc = -1;
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto cleanup;
|
||||
wwtest_fputs(r->src, f);
|
||||
int writebad = ferror(f);
|
||||
if (fclose(f) != 0) writebad = 1;
|
||||
if (writebad) goto cleanup;
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
|
||||
cleanup:
|
||||
{
|
||||
int cleanup_fail = 0;
|
||||
if (unlink(src) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n", r->label, src);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (unlink(cs) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n", r->label, cs);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (unlink(ws) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n", r->label, ws);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
fprintf(stderr, "row[%s]: cleanup %s failed\n",
|
||||
r->label, tmpdir);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (cleanup_fail && rc == 0) rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
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;
|
||||
|
||||
/* Compile+run for each (driver, row). */
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "cgreturn_struct: 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,
|
||||
"cgreturn_struct[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Asm byte-identity diff, only when both stages exist. */
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"cgreturn_struct: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("cgreturn_struct: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* 722_match_slice_variant — Class A asm-presence + byte-id sentinel
|
||||
* for task #19. Pins that the variant tag chosen for a `[]T` arm in a
|
||||
* `(scalar | []T)` tagged union matches between cstage and wwstage at
|
||||
* BOTH dispatch (cgmatch's `CMPQ $K, AX`) and call-site (the
|
||||
* `MOVQ $K, AX` widening that materialises the tag for the slice
|
||||
* payload).
|
||||
*
|
||||
* Pre-fix wwstage's name-keyed flatvariantidx / str-shape fallback
|
||||
* landed on tag 0 for any `[]T` lookup (pat.str == "" because the
|
||||
* variant node is N_TSLICE, not N_TNAME). Both the case arm and the
|
||||
* call-site emitted `$0` — internally consistent inside wwstage, but
|
||||
* the cstage-vs-wwstage cmp -s diverges from cstage's correct `$1`.
|
||||
* Class A: silent until wwstage path engaged. 967_bytes_run was
|
||||
* compiled through cstage only and never tripped; lib/strings landing
|
||||
* pulls bytes through wwstage and surfaces the dispatch miscompile
|
||||
* via 995_self_rebuild.
|
||||
*
|
||||
* Asserts cmp -s byte-id on the canonical (u8 | []u8) probe plus
|
||||
* other (scalar | []scalar) shapes from rob's matrix.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* The canonical (u8 | []u8) repro from bytes.index. */
|
||||
{ "u8_slice_u8",
|
||||
"fn pick(n: (u8 | []u8)) i32 = {\n"
|
||||
" match (n) {\n"
|
||||
" case let c: u8 => return 1;\n"
|
||||
" case let s: []u8 => return 2;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r1: i32 = pick(7u8);\n"
|
||||
" let buf: [2]u8;\n"
|
||||
" let r2: i32 = pick(buf[0:2]);\n"
|
||||
" if (r1 != 1) { return 11; };\n"
|
||||
" if (r2 != 2) { return 12; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
/* Reverse-order shape (slice arm first): the same shape-fallback
|
||||
* that lost (u8 | []u8) must also resolve ([]u8 | u8) without
|
||||
* accidentally inverting via the str-shape collapse. */
|
||||
{ "slice_u8_then_u8",
|
||||
"fn pick(n: ([]u8 | u8)) i32 = {\n"
|
||||
" match (n) {\n"
|
||||
" case let s: []u8 => return 1;\n"
|
||||
" case let c: u8 => return 2;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [2]u8;\n"
|
||||
" let r1: i32 = pick(buf[0:2]);\n"
|
||||
" let r2: i32 = pick(7u8);\n"
|
||||
" if (r1 != 1) { return 11; };\n"
|
||||
" if (r2 != 2) { return 12; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
/* Three-arm (u8 | []u8 | str): proves the slice axis composes with
|
||||
* the existing str axis (str arm at idx 2 stays at idx 2). */
|
||||
{ "u8_slice_u8_str",
|
||||
"fn pick(n: (u8 | []u8 | str)) i32 = {\n"
|
||||
" match (n) {\n"
|
||||
" case let c: u8 => return 1;\n"
|
||||
" case let s: []u8 => return 2;\n"
|
||||
" case let t: str => return 3;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [2]u8;\n"
|
||||
" if (pick(7u8) != 1) { return 11; };\n"
|
||||
" if (pick(buf[0:2]) != 2) { return 12; };\n"
|
||||
" if (pick(\"hi\") != 3) { return 13; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
};
|
||||
|
||||
static int
|
||||
emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap)
|
||||
{
|
||||
char src[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/msv_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(out_s, cap, "/tmp/msv_asm_%d_%d_%s.s",
|
||||
getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c");
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src);
|
||||
int rc = runwait(cmd);
|
||||
unlink(src);
|
||||
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 w6c[640], w6c_ww[640];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
int have_ww = (access(w6c_ww, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
char cs_path[128], ws_path[128];
|
||||
|
||||
if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) {
|
||||
fprintf(stderr,
|
||||
"match_slice_variant[cstage][%s]: w6c failed\n",
|
||||
rows[i].label);
|
||||
fail++; total++; continue;
|
||||
}
|
||||
|
||||
if (!have_ww) { unlink(cs_path); continue; }
|
||||
|
||||
if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) {
|
||||
fprintf(stderr,
|
||||
"match_slice_variant[wwstage][%s]: w6c_ww failed\n",
|
||||
rows[i].label);
|
||||
fail++; total++;
|
||||
unlink(cs_path);
|
||||
continue;
|
||||
}
|
||||
total++;
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"match_slice_variant[%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
|
||||
unlink(cs_path); unlink(ws_path);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"match_slice_variant: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("match_slice_variant: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* 725_nested_if_labels — sentinel for STATUS-3 #15. Cstage's
|
||||
* `cgen.c`-side per-function ≤24B-struct return path called
|
||||
* `mklabel(c, "retscr")`, consuming one `labelseq` counter slot per
|
||||
* function that returns a struct. Wwstage's equivalent emits a
|
||||
* fixed-name `@retscr` SSoT (mirrors `c.retscroff` in
|
||||
* selfhost/cmd/wcc/cgen.ww). Net effect pre-fix: every subsequent
|
||||
* control-flow label (`ct_N`, `ce_N`, `else_N`, `end_N`) in any such
|
||||
* function was 1 ahead in cstage.
|
||||
*
|
||||
* Filed STATUS-3 #15. Previously byte-id drift only; 995_self_rebuild
|
||||
* masked because the bootstrap main.combined.ww's drift was within
|
||||
* the threshold the test happens to allow. lib/strings (commit 1) +
|
||||
* lib/time.add nested-if shapes compounded the skew past that
|
||||
* threshold once worker-strings-v2 attempted the landing — promotes
|
||||
* #15 from latent to bootstrap-blocking.
|
||||
*
|
||||
* Fix aligns cstage DOWN to wwstage's single-slot SSoT (rule 10):
|
||||
* cgen.c's two `mklabel(c, "retscr")` sites now use the fixed string
|
||||
* "@retscr" passed to local_alloc. Labels never appear in emitted
|
||||
* asm — only the labelseq side-effect mattered, and dropping it
|
||||
* lets cstage's label numbering match wwstage's.
|
||||
*
|
||||
* Row pins asm `cmp -s` byte-id between stages on the canonical
|
||||
* time.add shape: ≤24B struct return + nested if/if/fallthrough.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Plain struct return + nested-if (time.add shape — struct
|
||||
* `instant` return, multi-arm if/if/fallthrough). Each return
|
||||
* site hits cgreturn's ≤24B-struct arm which allocates
|
||||
* @retscr; pre-fix mklabel(c, "retscr") bumped labelseq, post-
|
||||
* fix the fixed name leaves labelseq alone so cstage's
|
||||
* ct/ce/end numbering matches wwstage. */
|
||||
{ "struct_return_then_nested_if",
|
||||
"type instant = struct { sec: i64, nsec: i64 };\n"
|
||||
"export fn add(i: instant, x: i64) instant = {\n"
|
||||
" let r: instant;\n"
|
||||
" if (x == 0i64) {\n"
|
||||
" r.sec = i.sec; r.nsec = i.nsec;\n"
|
||||
" return r;\n"
|
||||
" };\n"
|
||||
" if (x > 0i64) {\n"
|
||||
" r.sec = i.sec + x; r.nsec = i.nsec;\n"
|
||||
" return r;\n"
|
||||
" };\n"
|
||||
" r.sec = i.sec - 1i64; r.nsec = i.nsec + x;\n"
|
||||
" return r;\n"
|
||||
"};\n" },
|
||||
};
|
||||
|
||||
static int
|
||||
emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap)
|
||||
{
|
||||
char src[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/nil_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(out_s, cap, "/tmp/nil_asm_%d_%d_%s.s",
|
||||
getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c");
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src);
|
||||
int rc = runwait(cmd);
|
||||
unlink(src);
|
||||
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 w6c[640], w6c_ww[640];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
int have_ww = (access(w6c_ww, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
char cs_path[128], ws_path[128];
|
||||
|
||||
if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) {
|
||||
fprintf(stderr,
|
||||
"nested_if_labels[cstage][%s]: w6c failed\n",
|
||||
rows[i].label);
|
||||
fail++; total++; continue;
|
||||
}
|
||||
total++;
|
||||
|
||||
if (!have_ww) { unlink(cs_path); continue; }
|
||||
|
||||
if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) {
|
||||
fprintf(stderr,
|
||||
"nested_if_labels[wwstage][%s]: w6c_ww failed\n",
|
||||
rows[i].label);
|
||||
fail++; total++;
|
||||
unlink(cs_path);
|
||||
continue;
|
||||
}
|
||||
total++;
|
||||
|
||||
/* Byte-id between stages — the fix-pin. Pre-fix cstage's
|
||||
* `add_ct_N` was 1 ahead of wwstage's; post-fix they match. */
|
||||
total++;
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"nested_if_labels[%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
|
||||
unlink(cs_path); unlink(ws_path);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"nested_if_labels: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("nested_if_labels: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* 736_cstage_label_ssot — sentinel for STATUS-4 #26. Cstage's tagged-
|
||||
* scratch sites previously stamped per-call labels via mklabel "tagbase"
|
||||
* / "tagscr" / "argscr" / "idxscr". Each call bumped `labelseq` and
|
||||
* allocated a fresh frame slot. Wwstage's mirrors use `@`-prefix names
|
||||
* routed through `localadd` (selfhost/cmd/wcc/cgen.ww) — the `@`-dedup
|
||||
* shares one slot per name per fn and never touches labelseq. Per-fn
|
||||
* `@tagbase` is one slot (8B); per-fn `@tagscr` is ONE slot shared
|
||||
* across cgwidentaggedstore via_outer (cgenutil.ww:2918), pushargsrev
|
||||
* struct-payload widen (cgenutil.ww:180) and cgindex tagged-element
|
||||
* (cgenexpr.ww:3524).
|
||||
*
|
||||
* Post-#26 cstage caches `cg_tagbase` + `cg_tagscr` (single shared slot
|
||||
* across the three @tagscr sites — matches wwstage's namespace exactly).
|
||||
* The size-strategy gap remains: wwstage's scanlocals pre-pass sizes
|
||||
* `c.tagscrsz` to the per-fn max BEFORE emit; cstage has no pre-pass,
|
||||
* so first call across the 3 sites sizes the slot and subsequent
|
||||
* callers must fit. fatal() per rule 7 if growth is needed. Long-term
|
||||
* convergence (wwstage DOWN to first-use+fail-loud on BOTH stages, or
|
||||
* cstage UP to a scanlocals pre-pass) is filed as #26c — separate
|
||||
* concern, not in #26's scope.
|
||||
*
|
||||
* succ_rows pin cstage vs wwstage asm `cmp -s` byte-id on the
|
||||
* canonical pointer-rooted two-tagged-store shapes. Pre-#26 cstage's
|
||||
* frame was 16B larger (extra @tagbase + @tagscr per call); post-#26
|
||||
* they match. The old fail_rows family pre-located the Class A
|
||||
* landmine (a fn needing the single @tagscr slot to GROW within one
|
||||
* cgfn → rule-7 fatal); #44 size-keys the scratch (@tagscr<sz>, one
|
||||
* slot per distinct slot size) so that shape now compiles — it moved
|
||||
* into succ_rows, and the dedicated runtime rows live in
|
||||
* 926_tagscr_sizes_run. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; };
|
||||
|
||||
static const struct row succ_rows[] = {
|
||||
/* Two struct-pointer-rooted tagged stores in the same fn. Each
|
||||
* `c.v = (...: bag);` enters cg_widen_tagged_store with a
|
||||
* non-BP base (D_BX, pointer-rooted dst) which routes through
|
||||
* the @tagbase spill + @tagscr scratch path. Pre-#26: 2 @tagbase
|
||||
* slots + 2 @tagscr slots in cstage's frame; wwstage shares one
|
||||
* @tagbase + one @tagscr via localadd `@`-dedup. Post-#26: cstage
|
||||
* caches both per-fn via cg_tagbase / cg_tagscr (mirrors
|
||||
* cg_retscr from #15-cstage) so the second call reuses, matching
|
||||
* wwstage. */
|
||||
{ "two_struct_ptr_tagged_stores",
|
||||
"type pair = struct { hi: i64, lo: i64 };\n"
|
||||
"type bag = (pair | i64);\n"
|
||||
"type cell = struct { v: bag };\n"
|
||||
"export fn run(c: *cell, p: pair, q: pair) i64 = {\n"
|
||||
" c.v = (p: bag);\n"
|
||||
" c.v = (q: bag);\n"
|
||||
" return 0i64;\n"
|
||||
"};\n" },
|
||||
/* Two pointer-rooted tagged stores into DIFFERENTLY-sized union
|
||||
* slots within one fn. Pre-#44 this was the fail_rows landmine:
|
||||
* the single first-use-sized @tagscr couldn't grow → cstage
|
||||
* fatal "@tagscr cached sz". #44 keys the scratch by slot size
|
||||
* (@tagscr<sz>), so each store pins its own slot and the shape
|
||||
* compiles byte-id on both stages. */
|
||||
{ "tagscr_two_sizes",
|
||||
"type small = (i64 | i32);\n"
|
||||
"type big = (i64 | str);\n"
|
||||
"type cell_a = struct { v: small };\n"
|
||||
"type cell_b = struct { v: big };\n"
|
||||
"export fn run(a: *cell_a, b: *cell_b, x: i64, s: str) i64 = {\n"
|
||||
" a.v = (x: small);\n"
|
||||
" b.v = (s: big);\n"
|
||||
" return 0i64;\n"
|
||||
"};\n" },
|
||||
};
|
||||
|
||||
static int
|
||||
emit_s(const char *w6c, const char *src_text, int i, char *out_s, size_t cap)
|
||||
{
|
||||
char src[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/lbl_ssot_%d_%d.ww", getpid(), i);
|
||||
snprintf(out_s, cap, "/tmp/lbl_ssot_%d_%d_%s.s",
|
||||
getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c");
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
wwtest_fputs(src_text, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src);
|
||||
int rc = runwait(cmd);
|
||||
unlink(src);
|
||||
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 w6c[640], w6c_ww[640];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
int have_ww = (access(w6c_ww, X_OK) == 0);
|
||||
|
||||
int total = 0, fail = 0;
|
||||
|
||||
int nsucc = (int)(sizeof succ_rows / sizeof succ_rows[0]);
|
||||
for (int i = 0; i < nsucc; i++) {
|
||||
char cs_path[128], ws_path[128];
|
||||
|
||||
if (emit_s(w6c, succ_rows[i].src, i, cs_path, sizeof cs_path) != 0) {
|
||||
fprintf(stderr,
|
||||
"cstage_label_ssot[cstage][%s]: w6c failed\n",
|
||||
succ_rows[i].label);
|
||||
fail++; total++; continue;
|
||||
}
|
||||
total++;
|
||||
|
||||
if (!have_ww) { unlink(cs_path); continue; }
|
||||
|
||||
if (emit_s(w6c_ww, succ_rows[i].src, i, ws_path, sizeof ws_path) != 0) {
|
||||
fprintf(stderr,
|
||||
"cstage_label_ssot[wwstage][%s]: w6c_ww failed\n",
|
||||
succ_rows[i].label);
|
||||
fail++; total++;
|
||||
unlink(cs_path);
|
||||
continue;
|
||||
}
|
||||
total++;
|
||||
|
||||
/* Byte-id between stages — the fix-pin. Pre-fix cstage's
|
||||
* frame was 16B larger and the second-store offsets pointed
|
||||
* at extra slots; post-fix they match. */
|
||||
total++;
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"cstage_label_ssot[%s]: cstage vs wwstage asm differs\n",
|
||||
succ_rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
|
||||
unlink(cs_path); unlink(ws_path);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"cstage_label_ssot: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("cstage_label_ssot: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
* 749_sumtype_forward — sentinel for #12.
|
||||
*
|
||||
* Pre-fix wwstage hardcoded the param's first-variant tag when
|
||||
* forwarding a sum-typed variadic element to a scalar (T|U) param.
|
||||
* cgindex emitted the tagged-CALL ABI (AX=tag, DX=word0, CX=word1[,
|
||||
* R8=word2]) for `needles[i]` correctly, then pushargsrev's widening
|
||||
* detection — keyed only on N_IDENT-source — fell into the scalar
|
||||
* widening branch: XORQ DX,DX / PUSHQ DX / PUSHQ AX / MOVQ $1,AX /
|
||||
* PUSHQ AX. The hardcoded `$1` made the callee always run the rune
|
||||
* arm; payload was the slice's first ptr word reinterpreted as a rune.
|
||||
*
|
||||
* cstage knew the arg's static type via check.c (`a->type == p->type`)
|
||||
* so the widen flag stayed off and the natural-push tagged-arg arm
|
||||
* pushed CX/DX/AX.
|
||||
*
|
||||
* Fix: wwstage pushargsrev now treats `arg.kind == N_INDEX &&
|
||||
* istaggedtype(indexvaluetnode(arg))` as already-tagged (matching
|
||||
* slot size to the param) and pushes CX/DX/AX in the natural-push
|
||||
* fallthrough.
|
||||
*
|
||||
* Rows assert per-stage runtime AND byte-id at the affected callsite.
|
||||
*
|
||||
* row | what it pins
|
||||
* -------------------------------+--------------------------------
|
||||
* forward_index_of_sum_variadic | #12 wedge. Pre-fix wwstage
|
||||
* | returned 11 (helper saw garbage
|
||||
* | rune); cstage returned 0.
|
||||
* | Post-fix both return 0.
|
||||
* forward_ident_sum | Sibling: forward an ident-source
|
||||
* | sum local to a sum-typed scalar
|
||||
* | param. Stays on the N_IDENT
|
||||
* | aistagged path — non-regression.
|
||||
* forward_literal_sum | Sibling: forward a literal-source
|
||||
* | value (str) to a sum-typed scalar
|
||||
* | param. Stays on the widening
|
||||
* | path — non-regression.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
/* Runtime ownership: test/wcc/data/r749_sumtype_forward_*. */
|
||||
const char *label;
|
||||
const char *src;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. Original #12 wedge. helper(needles[i]) where needles is
|
||||
* (str|rune)... — N_INDEX of sum-typed slice element forwarded
|
||||
* to a scalar (str|rune) param. pick("foo","world",'x') hits
|
||||
* the 'x' rune at i=2, so helper returns true and pick returns
|
||||
* true. main returns 0 on success. */
|
||||
{ "forward_index_of_sum_variadic",
|
||||
"package main;\n"
|
||||
"fn helper(needle: (str | rune)) bool = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let s: str => return s.len > 0;\n"
|
||||
" case let r: rune => return r == 'x';\n"
|
||||
" };\n"
|
||||
" return false;\n"
|
||||
"};\n"
|
||||
"fn pick(needles: (str | rune)...) bool = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < needles.len) {\n"
|
||||
" if (helper(needles[i])) { return true; };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return false;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (!pick(\"foo\", \"world\", 'x')) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
|
||||
/* 2. Ident-source sum-arg. needle is a (str|rune) local; helper
|
||||
* call routes through the existing N_IDENT-aistagged path. The
|
||||
* (str|rune) ident is built from a str literal so the rune arm
|
||||
* shouldn't fire. */
|
||||
{ "forward_ident_sum",
|
||||
"package main;\n"
|
||||
"fn helper(needle: (str | rune)) bool = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let s: str => return s.len == 3;\n"
|
||||
" case let r: rune => return false;\n"
|
||||
" };\n"
|
||||
" return false;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let needle: (str | rune) = \"foo\";\n"
|
||||
" if (!helper(needle)) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
|
||||
/* 3. Literal-source sum-arg. helper(\"foo\") widens the str at
|
||||
* the call site. Stays on the widening path (param tagged, arg
|
||||
* not tagged) and pushes a synthesised (tag=0, ptr, len) slot. */
|
||||
{ "forward_literal_sum",
|
||||
"package main;\n"
|
||||
"fn helper(needle: (str | rune)) bool = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let s: str => return s.len == 3;\n"
|
||||
" case let r: rune => return false;\n"
|
||||
" };\n"
|
||||
" return false;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (!helper(\"foo\")) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
};
|
||||
|
||||
/* read_file slurps a file whole, returning a malloc'd buffer
|
||||
* (NUL-terminated) or NULL on error. */
|
||||
static char *
|
||||
read_file(const char *path, size_t *outsz)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return NULL; }
|
||||
char *buf = malloc((size_t)n + 1);
|
||||
if (!buf) { fclose(f); return NULL; }
|
||||
size_t got = fread(buf, 1, (size_t)n, f);
|
||||
fclose(f);
|
||||
buf[got] = '\0';
|
||||
if (outsz) *outsz = got;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* slice_at finds the line beginning with `CALL\tmain.helper(SB)` and
|
||||
* returns a pointer to the start of the block four lines above (the
|
||||
* three PUSHQ + the tag MOV in the pre-fix case, the three PUSHQ in
|
||||
* the post-fix). For our byte-id check we compare a 64-byte window
|
||||
* starting at the first newline above that anchor. */
|
||||
static const char *
|
||||
helper_callsite(const char *asm_buf)
|
||||
{
|
||||
const char *anchor = strstr(asm_buf, "CALL\tmain.helper(SB)");
|
||||
if (!anchor) return NULL;
|
||||
/* walk back ~12 lines so the slice straddles the args set-up. */
|
||||
const char *p = anchor;
|
||||
int back = 12;
|
||||
while (back > 0 && p > asm_buf) {
|
||||
p--;
|
||||
if (*p == '\n') back--;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
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 cw6[640], ww6[640];
|
||||
snprintf(cw6, sizeof cw6, "%s/w6c", bin);
|
||||
snprintf(ww6, sizeof ww6, "%s/w6c_ww", bin);
|
||||
int have_w6cww = (access(ww6, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const struct row *r = &rows[i];
|
||||
|
||||
char src[160], tmpdir[96], css[160], wss[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/sumtype_fwd_%d_d_%d",
|
||||
getpid(), i);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[%s]: cannot acquire temp directory: %s\n",
|
||||
r->label, strerror(errno));
|
||||
fail++; total++;
|
||||
continue;
|
||||
}
|
||||
snprintf(src, sizeof src, "%s/sumtype_fwd_%d_%d.ww",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(css, sizeof css, "%s/cs.s", tmpdir);
|
||||
snprintf(wss, sizeof wss, "%s/ws.s", tmpdir);
|
||||
char *cs_asm = NULL, *ws_asm = NULL;
|
||||
size_t cs_n = 0, ws_n = 0;
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "sumtype_forward[%s]: cannot create source: %s\n",
|
||||
r->label, strerror(errno));
|
||||
fail++; total++;
|
||||
goto cleanup;
|
||||
}
|
||||
int write_failed = fputs(r->src, f) == EOF;
|
||||
if (fclose(f) != 0) write_failed = 1;
|
||||
if (write_failed) {
|
||||
fprintf(stderr, "sumtype_forward[%s]: cannot write source\n",
|
||||
r->label);
|
||||
fail++; total++;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Byte-id of the helper(needles[i]) callsite window. Emit
|
||||
* each stage's .s explicitly to a known path inside tmpdir
|
||||
* (w6c emits no .sepwork, so no leak) and compare. Only the
|
||||
* first row has a main.helper call; rows 2/3 also have one
|
||||
* but a simpler shape — match them all for symmetry. */
|
||||
if (have_w6cww) {
|
||||
char emit[1100];
|
||||
total++;
|
||||
snprintf(emit, sizeof emit, "%s -o %s %s 2>/dev/null",
|
||||
cw6, css, src);
|
||||
if (runwait(emit) != 0) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[cstage][%s]: w6c failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
cs_asm = read_file(css, &cs_n);
|
||||
if (cs_asm == NULL) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[cstage][%s]: cannot read %s\n",
|
||||
r->label, css);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(emit, sizeof emit, "%s -o %s %s 2>/dev/null",
|
||||
ww6, wss, src);
|
||||
if (runwait(emit) != 0) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[wwstage][%s]: w6c_ww failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
ws_asm = read_file(wss, &ws_n);
|
||||
if (ws_asm == NULL) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[wwstage][%s]: cannot read %s\n",
|
||||
r->label, wss);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
const char *cs_site = helper_callsite(cs_asm);
|
||||
const char *ws_site = helper_callsite(ws_asm);
|
||||
if (!cs_site || !ws_site) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[bid][%s]: no callsite anchor\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else if (strncmp(cs_site, ws_site, 96) != 0) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward[bid][%s]: callsite asm diverged\n",
|
||||
r->label);
|
||||
fprintf(stderr, " cs: %.96s\n", cs_site);
|
||||
fprintf(stderr, " ws: %.96s\n", ws_site);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup: ;
|
||||
free(cs_asm);
|
||||
free(ws_asm);
|
||||
int cleanup_fail = 0;
|
||||
const char *files[] = { src, css, wss };
|
||||
for (size_t k = 0; k < sizeof files / sizeof files[0]; k++) {
|
||||
if (unlink(files[k]) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sumtype_forward[%s]: unlink %s: %s\n",
|
||||
r->label, files[k], strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
fprintf(stderr, "sumtype_forward[%s]: rmdir %s: %s\n",
|
||||
r->label, tmpdir, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (cleanup_fail) fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"sumtype_forward: %d/%d rows failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("sumtype_forward: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* 765_star_fn_deref — root-cause lock for project #185. Pre-fix master
|
||||
* (5478695 baseline, post-#180), the cgen N_UN TK_STAR arm applied
|
||||
* the generic pointer-load `MOVQ (AX), AX` to a *fn operand. cgexpr
|
||||
* on the operand already left AX = fn-addr (post-#180 LEAQ); the
|
||||
* spurious second load then read the first instruction word and the
|
||||
* subsequent CALL AX jumped through that junk address, segfaulting.
|
||||
*
|
||||
* Fix: cmd/w6c/cgen.c N_UN TK_STAR opens with a TY_NAMED-peel +
|
||||
* TY_FN early-break — leave AX as the fn-addr cgexpr produced.
|
||||
* Selfhost twin in selfhost/cmd/wcc/cgenexpr.ww cgun TK_STAR uses the
|
||||
* same shape via tinfo.kind walk (TY_NAMED peel then TY_FN check),
|
||||
* mirroring ref/harec/src/check.c expr_call's
|
||||
* STORAGE_POINTER→STORAGE_FUNCTION path (harec skips the deref since
|
||||
* the pointer IS the address).
|
||||
*
|
||||
* #181 deferral: wwstage's checker bails asserttyped on the
|
||||
* `(*f)(...)` deref-call N_CALL shape (filed as project #181 — type_
|
||||
* stamp gap on the call-of-derefed-fn-ptr). Until #181 lands the
|
||||
* wwstage rows can't even reach cgen, so every row in this fixture
|
||||
* is gated CSTAGE-ONLY via stage_mask. Symmetry of the wwstage cgen
|
||||
* change is covered indirectly by the 990-997 self-build byte-id
|
||||
* gates (any cgen-side asymmetry would surface there); #181's own
|
||||
* probe will lock the wwstage runtime once the bail lifts.
|
||||
*
|
||||
* GATE-BLIND RISK (ken's note): byte-id alone cannot catch this
|
||||
* class — both stages drop the SAME instruction symmetrically, so
|
||||
* cs.s == ww.s holds either way. Runtime exit-code is the only
|
||||
* correctness net here, hence the multi-row table covering callee
|
||||
* shapes that exercise distinct ABI codepaths.
|
||||
*
|
||||
* Coverage:
|
||||
* 1. minimal — `let f = &add1; (*f)(7) == 8`
|
||||
* 2. branched_callee — pick aa or bb by runtime cond; verify
|
||||
* the SELECTED fn is actually invoked
|
||||
* (#105 lesson: single-callee masks via
|
||||
* address-constant aliasing)
|
||||
* 3. alias_chain — `let f = &fn; let g = f; (*g)(7)` —
|
||||
* catches if cgen re-evaluates *f after
|
||||
* the assignment
|
||||
* 4. fn_with_args — multiple args, scalar + ptr mix; ABI
|
||||
* register layout survives the deref-
|
||||
* call
|
||||
* 5. fn_tuple_return — (i64, str) return shape; multi-reg
|
||||
* return ABI survives the deref-call
|
||||
*
|
||||
* Gates per row:
|
||||
* a. cstage builds (exit 0); proves the checker stamps the
|
||||
* deref-call N_CALL on the cstage side.
|
||||
* b. cstage runs and exits with the row's expected_exit; pre-fix
|
||||
* every row was 139 (SIGSEGV).
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means either the cgen
|
||||
* TK_STAR TY_FN early-break regressed, or stage symmetry drifted
|
||||
* far enough to surface here (unlikely while wwstage rows stay
|
||||
* gated off).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* stage_mask bits — every row is cstage-only until #181 lifts the
|
||||
* wwstage asserttyped bail on `(*f)(x)`. STAGE_WW defined for
|
||||
* structural parity with 764. */
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expected_exit;
|
||||
int stage_mask;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "minimal",
|
||||
"fn add1(x: i32) i32 = { return x + 1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f: *fn(x: i32) i32 = &add1;\n"
|
||||
" return (*f)(7);\n"
|
||||
"};\n",
|
||||
8,
|
||||
STAGE_CS },
|
||||
/* Branched-callee: the if-arm forces a runtime choice between
|
||||
* two distinct fn addresses, defeating any constant-aliasing
|
||||
* mask of the deref miscompile (#105 lesson). */
|
||||
{ "branched_callee",
|
||||
"fn aa(x: i32) i32 = { return x; };\n"
|
||||
"fn bb(x: i32) i32 = { return x + 100; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let pick: i32 = 1;\n"
|
||||
" let f: *fn(x: i32) i32 = &aa;\n"
|
||||
" if (pick != 0) { f = &bb; };\n"
|
||||
" return (*f)(7);\n"
|
||||
"};\n",
|
||||
107,
|
||||
STAGE_CS },
|
||||
{ "alias_chain",
|
||||
"fn add1(x: i32) i32 = { return x + 1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f: *fn(x: i32) i32 = &add1;\n"
|
||||
" let g: *fn(x: i32) i32 = f;\n"
|
||||
" return (*g)(7);\n"
|
||||
"};\n",
|
||||
8,
|
||||
STAGE_CS },
|
||||
{ "fn_with_args",
|
||||
"fn many(a: i32, b: i32, p: *i32) i32 = { return a + b + *p; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let z: i32 = 5;\n"
|
||||
" let f: *fn(a: i32, b: i32, p: *i32) i32 = &many;\n"
|
||||
" return (*f)(3, 7, &z);\n"
|
||||
"};\n",
|
||||
15,
|
||||
STAGE_CS },
|
||||
/* Tuple-return: exercises the multi-register return ABI through
|
||||
* the deref-call. Tagged size 16B+ would also hit the AX:DX:CX
|
||||
* convention; (i64, str) keeps it simple while still covering
|
||||
* the multi-reg path. */
|
||||
{ "fn_tuple_return",
|
||||
"fn pair(x: i32) (i32, i32) = { return (x, x + 1); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f: *fn(x: i32) (i32, i32) = &pair;\n"
|
||||
" let a, b = (*f)(7);\n"
|
||||
" return a + b;\n"
|
||||
"};\n",
|
||||
15,
|
||||
STAGE_CS },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *src_path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(src_path, "wb");
|
||||
if (!f) return -1;
|
||||
wwtest_fputs(src, f);
|
||||
int rc = ferror(f) ? -1 : 0;
|
||||
if (fclose(f) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
cleanup_tmp(const char *tmpdir)
|
||||
{
|
||||
char p[512];
|
||||
int rc = 0;
|
||||
snprintf(p, sizeof p, "%s/main765.ww", tmpdir);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
snprintf(p, sizeof p, "%s/main765", tmpdir);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
snprintf(p, sizeof p, "rm -rf %s/main765.sepwork", tmpdir);
|
||||
if (runwait(p) != 0) rc = -1;
|
||||
if (rmdir(tmpdir) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *outbin, const char *src)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "timeout 180 %s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
/* run_row — build + run; returns the binary's exit code (-1 on
|
||||
* build failure). expected_exit comparison done at the call site. */
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int seq)
|
||||
{
|
||||
(void)seq;
|
||||
char tmpdir[] = "/tmp/wcsfd_XXXXXX";
|
||||
char src[256], outbin[512];
|
||||
if (mkdtemp(tmpdir) == NULL) return -1;
|
||||
snprintf(src, sizeof src, "%s/main765.ww", tmpdir);
|
||||
snprintf(outbin, sizeof outbin, "%s/main765", tmpdir);
|
||||
int rc = -1;
|
||||
if (write_source(src, r->src) != 0) {
|
||||
goto out;
|
||||
}
|
||||
if (build_via_driver(driver, outbin, src) == 0)
|
||||
rc = runwait(outbin);
|
||||
out:
|
||||
if (cleanup_tmp(tmpdir) != 0) {
|
||||
fprintf(stderr, "star_fn_deref[%s]: cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
int got = run_row(cdrv, &rows[i], seq++);
|
||||
if (got != rows[i].expected_exit) {
|
||||
fprintf(stderr,
|
||||
"star_fn_deref[cstage][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].expected_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
/* STAGE_WW path intentionally absent — see header #181
|
||||
* deferral note. When #181 lands and the wwstage runtime
|
||||
* gate flips on, mirror the 764 wdrv runwait pattern. */
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "star_fn_deref: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("star_fn_deref: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* 771_widen_transitive — residual artifact owner.
|
||||
*
|
||||
* Symmetric runtime and rejection coverage lives in wwfixture under
|
||||
* test/wcc/data/r771_*. This wrapper retains only compiler-output byte
|
||||
* identity plus the intentionally C-stage-only spread runtime row.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
const char *fixture;
|
||||
int byte_id;
|
||||
int cstage_run;
|
||||
int expected_exit;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "spread_alt_widen",
|
||||
"package main;\n"
|
||||
"type underread = !i32;\n"
|
||||
"type nomem = !void;\n"
|
||||
"type wrapper = !(underread | nomem);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let u: underread;\n"
|
||||
" let r: (size | ...wrapper) = u;\n"
|
||||
" if (r is underread) { return 70; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
NULL, 0, 1, 70 },
|
||||
{ "direct_flat_variant",
|
||||
NULL, "test/wcc/data/r771_direct_flat_variant/case.ww",
|
||||
1, 0, 71 },
|
||||
{ "branched_callee_widen",
|
||||
NULL, "test/wcc/data/r771_branched_callee_widen/case.ww",
|
||||
1, 0, 72 },
|
||||
{ "wrapper_typed_widen",
|
||||
NULL, "test/wcc/data/r771_wrapper_typed_widen/case.ww",
|
||||
1, 0, 73 },
|
||||
};
|
||||
|
||||
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
|
||||
write_source(const char *path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
int rc = fputs(src, f) == EOF ? -1 : 0;
|
||||
if (fclose(f) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
materialize_source(const char *cwd, const struct row *r, const char *dst)
|
||||
{
|
||||
if (r->src) return write_source(dst, r->src);
|
||||
char path[1024], buf[4096];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, r->fixture);
|
||||
FILE *in = fopen(path, "rb");
|
||||
if (!in) return -1;
|
||||
FILE *out = fopen(dst, "wb");
|
||||
if (!out) { fclose(in); return -1; }
|
||||
int rc = 0;
|
||||
for (;;) {
|
||||
size_t n = fread(buf, 1, sizeof buf, in);
|
||||
if (n != 0 && fwrite(buf, 1, n, out) != n) { rc = -1; break; }
|
||||
if (n < sizeof buf) {
|
||||
if (ferror(in)) rc = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fclose(out) != 0) rc = -1;
|
||||
fclose(in);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[640];
|
||||
int rc = 0;
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork", tmpdir, base);
|
||||
if (runwait(p) != 0) rc = -1;
|
||||
if (rmdir(tmpdir) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
|
||||
const char *src, int asm_only)
|
||||
{
|
||||
char cmd[2048];
|
||||
(void)cwd;
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && b='%s'; timeout 180 %s build %s"
|
||||
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
|
||||
tmpdir, src, driver, asm_only ? "-S " : "");
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_cstage(const char *driver, const char *cwd, const struct row *r, int seq)
|
||||
{
|
||||
(void)seq;
|
||||
char td[] = "/tmp/r771_r_XXXXXX";
|
||||
char src[512], base[64], exe[768];
|
||||
if (mkdtemp(td) == NULL) return -1;
|
||||
snprintf(base, sizeof base, "main771");
|
||||
snprintf(src, sizeof src, "%s/%s.ww", td, base);
|
||||
int rc = -1;
|
||||
if (materialize_source(cwd, r, src) != 0) goto out;
|
||||
rc = build_via_driver(driver, td, cwd, src, 0);
|
||||
if (rc == 0) {
|
||||
snprintf(exe, sizeof exe, "%s/%s", td, base);
|
||||
rc = runwait(exe);
|
||||
}
|
||||
out:
|
||||
if (cleanup_tmp(td, base) != 0) {
|
||||
fprintf(stderr, "widen_transitive[%s]: run cleanup failed\n",
|
||||
r->label);
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
|
||||
const struct row *r, int seq)
|
||||
{
|
||||
(void)seq;
|
||||
char tc[] = "/tmp/r771_c_XXXXXX";
|
||||
char tw[] = "/tmp/r771_w_XXXXXX";
|
||||
char src[512], base[64], cs[512], ws[512];
|
||||
snprintf(base, sizeof base, "main771");
|
||||
if (mkdtemp(tc) == NULL) return -1;
|
||||
if (mkdtemp(tw) == NULL) {
|
||||
if (cleanup_tmp(tc, base) != 0)
|
||||
fprintf(stderr, "widen_transitive[%s]: setup cleanup failed\n",
|
||||
r->label);
|
||||
return -1;
|
||||
}
|
||||
int rc = -1;
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tc, base);
|
||||
if (materialize_source(cwd, r, src) != 0) goto out;
|
||||
if (build_via_driver(cdrv, tc, cwd, src, 1) != 0) goto out;
|
||||
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tc, base);
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tw, base);
|
||||
if (materialize_source(cwd, r, src) != 0) goto out;
|
||||
if (build_via_driver(wdrv, tw, cwd, src, 1) != 0) goto out;
|
||||
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tw, base);
|
||||
FILE *fc = fopen(cs, "rb"), *fw = fopen(ws, "rb");
|
||||
if (fc && fw) {
|
||||
rc = 0;
|
||||
for (;;) {
|
||||
int a = fgetc(fc), b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
out:
|
||||
if (cleanup_tmp(tc, base) != 0) {
|
||||
fprintf(stderr, "widen_transitive[%s]: c cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
if (cleanup_tmp(tw, base) != 0) {
|
||||
fprintf(stderr, "widen_transitive[%s]: ww cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
char cwd[512];
|
||||
if (!getcwd(cwd, sizeof cwd)) return 1;
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cdrv[1200], wdrv[1200];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int fail = 0, total = 0, seq = 0;
|
||||
int wwpresent = access(wdrv, X_OK) == 0;
|
||||
for (size_t i = 0; i < sizeof rows / sizeof rows[0]; i++) {
|
||||
if (rows[i].cstage_run) {
|
||||
total++;
|
||||
int got = run_cstage(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].expected_exit) {
|
||||
fprintf(stderr, "widen_transitive[cstage-only run][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].expected_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && rows[i].byte_id) {
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr, "widen_transitive[byte-id][%s]: assembly differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!wwpresent) fprintf(stderr, "widen_transitive: skip byte-id (no %s)\n", wdrv);
|
||||
if (fail) {
|
||||
fprintf(stderr, "widen_transitive: %d/%d residual gates failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("widen_transitive: %d/%d residual gates ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
/*
|
||||
* 779_log_vstream_run — project #94 fold-eFinal sentinel. Pins the
|
||||
* single-surface lib/log: module-static stderrsink_ctx_g with
|
||||
* `vt: io.vtable` as the first field for the intrusive cast, the 10
|
||||
* exported variants of log.ww's surface (new / lprintln / println /
|
||||
* lprintfln / printfln / lfatal / fatal / lfatalf / fatalf /
|
||||
* setlogger), a logger vtable + stdlogger over an io.stream sink, and
|
||||
* lazy ensureinit wiring the stderr default + silent / default /
|
||||
* global *logger globals. The fold-eFinal FLIP collapsed the dual
|
||||
* surface into this one Hare-shaped surface (bare names, no suffix).
|
||||
*
|
||||
* Each row imports os + log + memio + io. log itself imports fmt,
|
||||
* so the formattable / field types flow through transitively.
|
||||
*
|
||||
* Cstage-only per row (no STAGE_WW, no byte_id). #209 (the wwstage
|
||||
* checker bail on match-arm-over-formattable when fmt is imported
|
||||
* transitively — which lib/log does for the logger vtable signatures +
|
||||
* the fmt.fprint / fprintf dispatch in stdprintln / stdprintfln) is now
|
||||
* CLOSED; wwstage compiles fmt/log. The residual blocker is the
|
||||
* pre-existing cgen cluster #226 (io.read error-remap nominal-identity)
|
||||
* + #227 (spread-union widen/return-ABI), surfaced once fmt codegens
|
||||
* under wwstage. Existing 971 logtest runs the @test fixture under
|
||||
* cstage `ww run` only. 995 self-rebuild dodges it because no selfhost
|
||||
* cmd imports log transitively. Byte-id graduates when #226 (+#227) land.
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* println_v_default_stderr | log.println through the [[default]]
|
||||
* | global, routed to fd 2 via the
|
||||
* | module-static stderrsink_ctx_g. Asserts
|
||||
* | ensureinit wires + dispatches without
|
||||
* | crashing; stderr capture is left to the
|
||||
* | shell runner. Pins the full chain:
|
||||
* | println → lprintln(global) →
|
||||
* | (*logger).println → stdprintln →
|
||||
* | fmt.fprint + io.write("\n").
|
||||
* printfln_v_default_stderr | Same chain as row 1 but through the
|
||||
* | format-string slot (printfln →
|
||||
* | lprintfln(global) → stdprintfln
|
||||
* | → fmt.fprintf + io.write("\n")).
|
||||
* | Pins the {n}-placeholder dispatch
|
||||
* | through vformatfield reaches the
|
||||
* | default sink.
|
||||
* lprintln_v_custom_sink | memio.fixed-backed stdlogger;
|
||||
* | log.new wires the vtable + sink,
|
||||
* | log.lprintln dispatches "hi" + 7i64
|
||||
* | through stdprintln. Asserts the
|
||||
* | exact bytes ("hi 7\n") land in the
|
||||
* | caller's buffer (fixed borrows
|
||||
* | the slice, so the bytes are visible
|
||||
* | back through buf[]).
|
||||
* branched_lprintln | branched callee per #105: two distinct
|
||||
* | stdlogger sinks runtime-selected via
|
||||
* | *logger pointer. lprintln dispatches
|
||||
* | through the picked sink only; the
|
||||
* | other stays empty. Catches a constant-
|
||||
* | fold mistake in the fn-ptr dispatch
|
||||
* | (mirror of 778's branched_bufio_wrap).
|
||||
*
|
||||
* DEFERRALS / RELATED (NOT addressed by this row):
|
||||
*
|
||||
* - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit
|
||||
* in lib/log/log.ww; the explicit casts are KEPT (ken/#214 —
|
||||
* cast-drop is a separate deferred payoff gated on #214).
|
||||
*
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): stderrwrite
|
||||
* constructs nomem and widens to io.error explicitly rather than
|
||||
* using `os.trywrite(...)?`; same shape memio.ww + fmt.ww +
|
||||
* bufio.ww adopt.
|
||||
*
|
||||
* - #226 (io.read error-remap nominal-identity) + #227 (spread-union
|
||||
* widen/return-ABI): every row is STAGE_CS-only; byte-id deferred
|
||||
* until they land. #209 (the checker bail) is already closed.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: log is NOT embedded in any selfhost
|
||||
* combined.ww (grep `package log\|import log` returns empty), so the
|
||||
* fold-eFinal log collapse does not require a Makefile regen (#110);
|
||||
* only lib/log/ + lib/fmt/ test paths see the surface. 990-997
|
||||
* byte-id gates stay green by virtue of log being test-only.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means lib/log
|
||||
* regressed, the module-static stderrsink_ctx_g zero-init or chained
|
||||
* vt-field assigns regressed, the intrusive io.stream→*stderrsink_ctx
|
||||
* cast miscomputed offsets, ensureinit's one-shot guard broke, or
|
||||
* the fn-ptr dispatch through logger.println / printfln stopped
|
||||
* resolving.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want_exit;
|
||||
int stage_mask;
|
||||
int byte_id;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "println_v_default_stderr",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" log.println(\"hello\");\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS, 0 },
|
||||
{ "printfln_v_default_stderr",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" log.printfln(\"v={}\", 7i64);\n"
|
||||
" return 43;\n"
|
||||
"};\n",
|
||||
43,
|
||||
STAGE_CS, 0 },
|
||||
{ "lprintln_v_custom_sink",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
"import memio;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let vs: io.stream = &st.vt;\n"
|
||||
" let sl: log.stdlogger = log.new(vs);\n"
|
||||
" log.lprintln(&sl.logger, \"hi\", 7i64);\n"
|
||||
" if (buf[0] != 104u8) { return 91; };\n"
|
||||
" if (buf[1] != 105u8) { return 92; };\n"
|
||||
" if (buf[2] != 32u8) { return 93; };\n"
|
||||
" if (buf[3] != 55u8) { return 94; };\n"
|
||||
" if (buf[4] != 10u8) { return 95; };\n"
|
||||
" return 44;\n"
|
||||
"};\n",
|
||||
44,
|
||||
STAGE_CS, 0 },
|
||||
{ "branched_lprintln",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
"import memio;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let bufA: [16]u8;\n"
|
||||
" let bufB: [16]u8;\n"
|
||||
" let stA: memio.stream = memio.fixed(bufA[0:16]);\n"
|
||||
" let vsA: io.stream = &stA.vt;\n"
|
||||
" let stB: memio.stream = memio.fixed(bufB[0:16]);\n"
|
||||
" let vsB: io.stream = &stB.vt;\n"
|
||||
" let slA: log.stdlogger = log.new(vsA);\n"
|
||||
" let slB: log.stdlogger = log.new(vsB);\n"
|
||||
" let sel: i32 = 1;\n"
|
||||
" let chosen: *log.logger = &slA.logger;\n"
|
||||
" if (sel == 0) { chosen = &slB.logger; };\n"
|
||||
" log.lprintln(chosen, \"pick\");\n"
|
||||
" if (bufA[0] != 112u8) { return 91; };\n"
|
||||
" if (bufA[1] != 105u8) { return 92; };\n"
|
||||
" if (bufA[2] != 99u8) { return 93; };\n"
|
||||
" if (bufA[3] != 107u8) { return 94; };\n"
|
||||
" if (bufA[4] != 10u8) { return 95; };\n"
|
||||
" if (bufB[0] != 0u8) { return 96; };\n"
|
||||
" return 45;\n"
|
||||
"};\n",
|
||||
45,
|
||||
STAGE_CS, 0 },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
int bad = fputs(src, f) == EOF || ferror(f);
|
||||
if (fclose(f) != 0) bad = 1;
|
||||
return bad ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Per-row tmpdir cleanup. `ww build` retains its exact
|
||||
* <base>.sepwork tree; remove that tree, the source, and the bare
|
||||
* executable before removing the invocation-owned directory. */
|
||||
static int
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[640];
|
||||
int rc = 0;
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
snprintf(p, sizeof p, "rm -rf -- %s/%s.sepwork", tmpdir, base);
|
||||
if (runwait(p) != 0) rc = -1;
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
if (rmdir(tmpdir) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
|
||||
const char *src)
|
||||
{
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null",
|
||||
tmpdir, driver, cwd, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[512], base[64], outbin[768];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/lvs_%d_d_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main779");
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
||||
if (mkdir(tmpdir, 0755) != 0) return -1;
|
||||
if (write_source(src, r->src) != 0) {
|
||||
if (cleanup_tmp(tmpdir, base) != 0)
|
||||
fprintf(stderr, "row[%s]: setup cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
int rc;
|
||||
int br = build_via_driver(driver, tmpdir, cwd, src);
|
||||
if (br == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
/* Suppress stderr for rows 1/2 — the default-sink probes
|
||||
* write through fd 2; don't pollute the test runner's
|
||||
* stderr. */
|
||||
char runcmd[1024];
|
||||
snprintf(runcmd, sizeof runcmd, "%s 2>/dev/null", outbin);
|
||||
rc = runwait(runcmd);
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
if (cleanup_tmp(tmpdir, base) != 0) return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees keep
|
||||
* each driver's retained .sepwork output separate. Unused
|
||||
* for fold-e5 — every row is STAGE_CS-only per #226/#227 (#209, the
|
||||
* checker bail, is closed) — but kept scaffolded for when those cgen
|
||||
* folds land and byte-id graduates. */
|
||||
static int
|
||||
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
|
||||
const struct row *r, int seq)
|
||||
{
|
||||
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
|
||||
snprintf(tdc, sizeof tdc, "/tmp/lvs_%d_c_%d", getpid(), seq);
|
||||
snprintf(tdw, sizeof tdw, "/tmp/lvs_%d_w_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main779");
|
||||
if (mkdir(tdc, 0755) != 0) return -1;
|
||||
if (mkdir(tdw, 0755) != 0) {
|
||||
if (cleanup_tmp(tdc, base) != 0)
|
||||
fprintf(stderr, "row[%s]: setup cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
|
||||
if (write_source(src, r->src) != 0) {
|
||||
int clean_c = cleanup_tmp(tdc, base);
|
||||
int clean_w = cleanup_tmp(tdw, base);
|
||||
if (clean_c != 0 || clean_w != 0)
|
||||
fprintf(stderr, "row[%s]: setup cleanup failed\n", r->label);
|
||||
return -1;
|
||||
}
|
||||
int rc = -1;
|
||||
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
|
||||
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
|
||||
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
|
||||
if (write_source(src, r->src) != 0) goto out;
|
||||
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
|
||||
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
if (fc && fw) {
|
||||
rc = 0;
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
out:
|
||||
if (cleanup_tmp(tdc, base) != 0) rc = -1;
|
||||
if (cleanup_tmp(tdw, base) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
int got = run_row(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"log_vstream_run[cs][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
int got = run_row(wdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"log_vstream_run[ww][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (rows[i].byte_id) {
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"log_vstream_run[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "log_vstream_run: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "log_vstream_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("log_vstream_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,438 +0,0 @@
|
||||
/*
|
||||
* 952_floats_run — runtime regression net for lib/math/floats fold-1
|
||||
* (f64bits, f64frombits, isnan, isinf, signf64, absf64, copysignf64,
|
||||
* ispositivef64, isnegativef64) plus the fold-2a decompose step
|
||||
* (issubnormalf64, normalizef64, frexpf64). The classify/sign/bits surface rides
|
||||
* the f64-codegen paths fixed by #96 (deref-load -> MOVSD/X0) and #97
|
||||
* (compare consults PF for NaN); normalizef64 additionally rides the #102
|
||||
* 16B-tuple-from-call receive (its (f64, i64) return) and the f64 multiply
|
||||
* on the subnormal path. Both stages emit byte-identical asm, so the
|
||||
* 990-997 byte-id gates can't catch a reintroduction — only an
|
||||
* executed-and-checked runtime probe can. This file is that probe.
|
||||
*
|
||||
* frexpf64 rides two now-landed cgen fixes: the `n == 0f64` zero guard
|
||||
* (#103 — a no-decimal f64 literal now reaches XMM) and its (f64, i64)
|
||||
* tuple return + .0/.1 destructure (#105 — the tuple f64-word read). The
|
||||
* frexpf64(0.0) row is the key regressor: it exercises both the early
|
||||
* return AND the tuple receive. As with the normalizef64 rows, tuple
|
||||
* fields are spilled to a let before any f64-literal comparison (a
|
||||
* tuple-field f64 compared directly against a literal `r.0 == 0.0` was the
|
||||
* #105 mis-load); the .1 i64 field is compared in place.
|
||||
*
|
||||
* Table-driven like 951_f64cgen_run: each row is a self-contained ww
|
||||
* program importing math; the C-side cstage `ww build -I lib` compiles
|
||||
* it, we run the binary and assert the exit code. Self-checking rows
|
||||
* return 0 on pass / a locator code on the first failing assertion;
|
||||
* value-propagation rows return a computed result compared to want_exit.
|
||||
*
|
||||
* cstage-only by design (mirrors 951 + 969_checked_run): `ww_ww run` is
|
||||
* broken (#95) and per-program wwstage byte-id is the 990-997 gates' job.
|
||||
*
|
||||
* NaN/INF operands are built at runtime through opaque zero()/one() fns
|
||||
* so the checker can't const-fold them away (matches 951's #97 rows).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct row { const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* f64bits + f64frombits round-trip. 0x3FF0000000000000 == 1.0,
|
||||
* 0x4045000000000000 == 42.0. Round-trip through tobits/frombits
|
||||
* must preserve the bit pattern (both u64 and f64 deref-loads). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.f64bits(1.0) != 0x3FF0000000000000u64) { return 1; };\n"
|
||||
" if (math.f64bits(0.0) != 0u64) { return 2; };\n"
|
||||
" let rt: f64 = math.f64frombits(math.f64bits(-2.5));\n"
|
||||
" if (rt != -2.5) { return 3; };\n"
|
||||
" if (math.f64frombits(0x4045000000000000u64) != 42.0) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* f64frombits value propagation: a u64 bit pattern reinterpreted as
|
||||
* f64 then truncated to i32. On #96 the deref strands the value in a
|
||||
* GPR and the i32 cast reads stale X0. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return math.f64frombits(0x4045000000000000u64): i32;\n"
|
||||
"};\n", 42 },
|
||||
/* isnan + isinf. Runtime NaN/INF via opaque zero()/one() so the
|
||||
* checker can't const-fold. isnan rides #97 (self-inequality must
|
||||
* be true for NaN); isinf rides #96 (f64bits deref-load). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"fn zero() f64 = { return 0.0; };\n"
|
||||
"fn one() f64 = { return 1.0; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let nan: f64 = zero() / zero();\n"
|
||||
" if (!math.isnan(nan)) { return 1; };\n"
|
||||
" if (math.isnan(1.0)) { return 2; };\n"
|
||||
" let inf: f64 = one() / zero();\n"
|
||||
" if (!math.isinf(inf)) { return 3; };\n"
|
||||
" if (!math.isinf(-inf)) { return 4; };\n"
|
||||
" if (math.isinf(nan)) { return 5; };\n"
|
||||
" if (math.isinf(1.23)) { return 6; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* signf64 + ispositivef64 + isnegativef64. +0.0 has a clear sign
|
||||
* bit so signf64(0.0) == 1 (zero is positive). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.signf64(3.0) != 1i64) { return 1; };\n"
|
||||
" if (math.signf64(-3.0) != -1i64) { return 2; };\n"
|
||||
" if (math.signf64(0.0) != 1i64) { return 3; };\n"
|
||||
" if (!math.ispositivef64(3.0)) { return 4; };\n"
|
||||
" if (math.ispositivef64(-3.0)) { return 5; };\n"
|
||||
" if (!math.isnegativef64(-3.0)) { return 6; };\n"
|
||||
" if (math.isnegativef64(3.0)) { return 7; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* absf64 + copysignf64. absf64(NaN) stays NaN (early return before
|
||||
* the bit-mask). copysign transfers y's sign onto x's magnitude. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"fn zero() f64 = { return 0.0; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.absf64(-3.0) != 3.0) { return 1; };\n"
|
||||
" if (math.absf64(3.0) != 3.0) { return 2; };\n"
|
||||
" let nan: f64 = zero() / zero();\n"
|
||||
" if (!math.isnan(math.absf64(nan))) { return 3; };\n"
|
||||
" if (math.copysignf64(3.0, -1.0) != -3.0) { return 4; };\n"
|
||||
" if (math.copysignf64(-3.0, 1.0) != 3.0) { return 5; };\n"
|
||||
" if (math.copysignf64(3.0, 1.0) != 3.0) { return 6; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* absf64 value propagation: f64 result truncated to i32 (rides #96
|
||||
* f64-return through X0). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return math.absf64(-7.0): i32;\n"
|
||||
"};\n", 7 },
|
||||
/* issubnormalf64: normals and zero are not subnormal; the smallest
|
||||
* positive f64 (bits == 1, ~5e-324) is. Built via f64frombits so the
|
||||
* checker can't fold a subnormal literal (which it would flush). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.issubnormalf64(16.0)) { return 1; };\n"
|
||||
" if (math.issubnormalf64(0.0)) { return 2; };\n"
|
||||
" if (math.issubnormalf64(1.0)) { return 3; };\n"
|
||||
" let s: f64 = math.f64frombits(1u64);\n"
|
||||
" if (!math.issubnormalf64(s)) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* normalizef64 on a normal value returns (n, 0). The (f64, i64) tuple
|
||||
* rides the #102 16B-tuple-from-call receive; r.0 is spilled to a let
|
||||
* before comparison (see header). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" const r = math.normalizef64(16.0);\n"
|
||||
" const m: f64 = r.0;\n"
|
||||
" if (m != 16.0) { return 1; };\n"
|
||||
" if (r.1 != 0i64) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* normalizef64 on a subnormal multiplies by 2^52 (f64 multiply inside a
|
||||
* tuple return) and returns exp -52; the result is no longer subnormal
|
||||
* and is nonzero. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: f64 = math.f64frombits(1u64);\n"
|
||||
" const r = math.normalizef64(s);\n"
|
||||
" if (r.1 != -52i64) { return 1; };\n"
|
||||
" const m: f64 = r.0;\n"
|
||||
" if (math.issubnormalf64(m)) { return 2; };\n"
|
||||
" if (m == 0.0) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* normalizef64 value propagation: normal-path .0 truncated to i32
|
||||
* (spilled to a let to dodge the tuple-field-compare mis-load; the
|
||||
* cast itself reads the field fine). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" const r = math.normalizef64(42.0);\n"
|
||||
" const m: f64 = r.0;\n"
|
||||
" return m: i32;\n"
|
||||
"};\n", 42 },
|
||||
/* frexpf64 exact (mantissa, exp): 8.0 -> (0.5, 4), 1.0 -> (0.5, 1),
|
||||
* 0.75 -> (0.75, 0), and the key 0.0 -> (0.0, 0) early-return row
|
||||
* (rides #103 `n == 0f64` + #105 tuple receive). Tuple .0 spilled to a
|
||||
* let before the f64-literal compare; .1 compared in place. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" const a = math.frexpf64(8.0);\n"
|
||||
" const am: f64 = a.0;\n"
|
||||
" if (am != 0.5) { return 1; };\n"
|
||||
" if (a.1 != 4i64) { return 2; };\n"
|
||||
" const b = math.frexpf64(1.0);\n"
|
||||
" const bm: f64 = b.0;\n"
|
||||
" if (bm != 0.5) { return 3; };\n"
|
||||
" if (b.1 != 1i64) { return 4; };\n"
|
||||
" const c = math.frexpf64(0.0);\n"
|
||||
" const cm: f64 = c.0;\n"
|
||||
" if (cm != 0.0) { return 5; };\n"
|
||||
" if (c.1 != 0i64) { return 6; };\n"
|
||||
" const d = math.frexpf64(0.75);\n"
|
||||
" const dm: f64 = d.0;\n"
|
||||
" if (dm != 0.75) { return 7; };\n"
|
||||
" if (d.1 != 0i64) { return 8; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* frexpf64 mantissa range + reconstruction: for a nonzero input the
|
||||
* mantissa is in [0.5, 1) and mantissa * 2^exp reproduces n. 12.0 ->
|
||||
* (0.75, 4); 0.75 * 16 (== 2^4) == 12. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" const r = math.frexpf64(12.0);\n"
|
||||
" const m: f64 = r.0;\n"
|
||||
" if (m < 0.5) { return 1; };\n"
|
||||
" if (m >= 1.0) { return 2; };\n"
|
||||
" if (r.1 != 4i64) { return 3; };\n"
|
||||
" if (m * 16.0 != 12.0) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* frexpf64 value propagation: 1024.0 == 2^10 -> (0.5, 11); the i64 exp
|
||||
* field cast to i32 propagates as 11. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" const r = math.frexpf64(1024.0);\n"
|
||||
" return r.1: i32;\n"
|
||||
"};\n", 11 },
|
||||
/* --- strconv-foundation fold-1: F32 width constants. Mirror
|
||||
* ref/hare/math/floats.ha:27,30,33,43,46. The shape values (23/8/
|
||||
* 127, 0x7FFFFF, 0xFF) are facts of IEEE 754 binary32 — any drift
|
||||
* here means F32_* def-folding broke. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.F32_MANTISSA_BITS != 23u32) { return 1; };\n"
|
||||
" if (math.F32_EXPONENT_BITS != 8u32) { return 2; };\n"
|
||||
" if (math.F32_EXPONENT_BIAS != 127u32) { return 3; };\n"
|
||||
" if (math.F32_MANTISSA_MASK != 0x7FFFFFu32) { return 4; };\n"
|
||||
" if (math.F32_EXPONENT_MASK != 0xFFu32) { return 5; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* f32bits / f32frombits round-trip. Mirror
|
||||
* ref/hare/math/floats.ha:8,14 + ref/hare/math/+test/floats_test.ha:4
|
||||
* (the f32 leg). 1.0f32 -> 0x3F800000, 2.0f32 -> 0x40000000,
|
||||
* -1.0f32 -> 0xBF800000, 0.0f32 -> 0u32. Rides the f32-literal
|
||||
* materialise path (#104 fold-1) and the f32 ptr-deref read/write. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.f32bits(1.0f32) != 0x3F800000u32) { return 1; };\n"
|
||||
" if (math.f32bits(2.0f32) != 0x40000000u32) { return 2; };\n"
|
||||
" if (math.f32bits(-1.0f32) != 0xBF800000u32) { return 3; };\n"
|
||||
" if (math.f32bits(0.0f32) != 0u32) { return 4; };\n"
|
||||
" if (math.f32frombits(0x3F800000u32) != 1.0f32) { return 5; };\n"
|
||||
" if (math.f32frombits(0x40000000u32) != 2.0f32) { return 6; };\n"
|
||||
" if (math.f32frombits(0xBF800000u32) != -1.0f32) { return 7; };\n"
|
||||
" if (math.f32frombits(0u32) != 0.0f32) { return 8; };\n"
|
||||
" let v: f32 = 123456.0f32;\n"
|
||||
" if (math.f32frombits(math.f32bits(v)) != v) { return 9; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* f32bits value propagation: bit pattern 0x42280000 == 42.0f32; the
|
||||
* u32 cast to i32 propagates as 42. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return math.f32frombits(0x42280000u32): i32;\n"
|
||||
"};\n", 42 },
|
||||
/* floatinfo struct: f64-shape instance, address-of must round-trip
|
||||
* through a *floatinfo pointer parameter — that's the call shape
|
||||
* stof's eisel_lemire (`f: *floatinfo`) uses in fold-4. Mirrors
|
||||
* ref/hare/math/floats.ha:103. Stack-local because module-level
|
||||
* struct lets-with-init don't lower yet (gap reported with lead).
|
||||
* Field assignments rather than a struct literal: ww's N_DOT-qualified
|
||||
* `math.floatinfo { ... }` literal trips the cross-module resolver
|
||||
* (#16/#17, task list #12). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"fn readbits(p: *math.floatinfo) u64 = { return p.mantbits; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let info: math.floatinfo;\n"
|
||||
" info.mantbits = 52u64;\n"
|
||||
" info.expbits = 11u64;\n"
|
||||
" info.expbias = 1023;\n"
|
||||
" info.mantmask = 0xFFFFFFFFFFFFFu64;\n"
|
||||
" info.expmask = 0x7FFu64;\n"
|
||||
" if (info.mantbits != 52u64) { return 1; };\n"
|
||||
" if (info.expbits != 11u64) { return 2; };\n"
|
||||
" if (info.expbias != 1023) { return 3; };\n"
|
||||
" if (info.mantmask != 0xFFFFFFFFFFFFFu64) { return 4; };\n"
|
||||
" if (info.expmask != 0x7FFu64) { return 5; };\n"
|
||||
" if (readbits(&info) != 52u64) { return 6; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* floatinfo f32-shape instance. Same fold as above; verifies the
|
||||
* struct type is width-agnostic (Hare exports both f64info and
|
||||
* f32info, ref/hare/math/floats.ha:117,126). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"fn readbits(p: *math.floatinfo) u64 = { return p.mantbits; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let info: math.floatinfo;\n"
|
||||
" info.mantbits = 23u64;\n"
|
||||
" info.expbits = 8u64;\n"
|
||||
" info.expbias = 127;\n"
|
||||
" info.mantmask = 0x7FFFFFu64;\n"
|
||||
" info.expmask = 0xFFu64;\n"
|
||||
" if (info.mantbits != 23u64) { return 1; };\n"
|
||||
" if (info.expbias != 127) { return 2; };\n"
|
||||
" if (readbits(&info) != 23u64) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* --- strconv-foundation fold-1b: NAN_BITS / INF_BITS sentinels.
|
||||
* Hare exports `def NAN = 0.0/0.0;` / `def INF = 1.0/0.0;` (ref/hare/
|
||||
* math/floats.ha:137,141); ww materializes via f64frombits(NAN_BITS)
|
||||
* / f64frombits(INF_BITS) until #129 closes. The bit values are
|
||||
* IEEE-754 facts (quiet-NaN: exp-all-ones + mantissa-MSB; +Inf:
|
||||
* exp-all-ones + mantissa-zero) — any drift means the def-folded
|
||||
* sentinels broke. */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (math.NAN_BITS != 0x7FF8000000000000u64) { return 1; };\n"
|
||||
" if (math.INF_BITS != 0x7FF0000000000000u64) { return 2; };\n"
|
||||
" let n: f64 = math.f64frombits(math.NAN_BITS);\n"
|
||||
" if (!math.isnan(n)) { return 3; };\n"
|
||||
" if (math.isnan(1.0)) { return 4; };\n"
|
||||
" let i: f64 = math.f64frombits(math.INF_BITS);\n"
|
||||
" if (!math.isinf(i)) { return 5; };\n"
|
||||
" if (!math.isinf(-i)) { return 6; };\n"
|
||||
" if (math.isinf(1.23)) { return 7; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* f64info / f32info module-scope instances, read cross-module through
|
||||
* a *floatinfo pointer parameter — the exact call shape stof's
|
||||
* eisel_lemire (`f: *floatinfo`) uses in fold-4. Address-of a
|
||||
* module-level def lowers to LEAQ since #149; the field reads
|
||||
* round-trip the static-init DATA the #129 A.2 path emits. Mirrors
|
||||
* ref/hare/math/floats.ha:117,126. Pointer-param (not direct
|
||||
* math.f64info.expbias) because a cross-module struct-field direct read
|
||||
* leaks the field name as an extern (#150, task list #48). */
|
||||
{ "package main;\n"
|
||||
"import math;\n"
|
||||
"fn rdbias(f: *math.floatinfo) int = { return f.expbias; };\n"
|
||||
"fn rdmant(f: *math.floatinfo) u64 = { return f.mantbits; };\n"
|
||||
"fn rdmask(f: *math.floatinfo) u64 = { return f.mantmask; };\n"
|
||||
"fn rdebits(f: *math.floatinfo) u64 = { return f.expbits; };\n"
|
||||
"fn rdemask(f: *math.floatinfo) u64 = { return f.expmask; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (rdbias(&math.f64info) != 1023) { return 1; };\n"
|
||||
" if (rdmant(&math.f64info) != 52u64) { return 2; };\n"
|
||||
" if (rdmask(&math.f64info) != 0xFFFFFFFFFFFFFu64) { return 3; };\n"
|
||||
" if (rdbias(&math.f32info) != 127) { return 4; };\n"
|
||||
" if (rdmant(&math.f32info) != 23u64) { return 5; };\n"
|
||||
" if (rdmask(&math.f32info) != 0x7FFFFFu64) { return 6; };\n"
|
||||
" if (rdebits(&math.f64info) != 11u64) { return 7; };\n"
|
||||
" if (rdemask(&math.f64info) != 0x7FFu64) { return 8; };\n"
|
||||
" if (rdebits(&math.f32info) != 8u64) { return 9; };\n"
|
||||
" if (rdemask(&math.f32info) != 0xFFu64) { return 10; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char tmpdir[] = "/tmp/wwflt_XXXXXX";
|
||||
if (mkdtemp(tmpdir) == NULL) {
|
||||
fprintf(stderr, "row %d: mkdtemp failed\n", i);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int rowfail = 0;
|
||||
char src[128], outbin[128], cmd[2048];
|
||||
snprintf(src, sizeof src, "%s/case.ww", tmpdir);
|
||||
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "row %d: source open failed\n", i);
|
||||
rowfail = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
int writefail = fputs(rows[i].src, f) == EOF;
|
||||
if (fclose(f) != 0) writefail = 1;
|
||||
if (writefail) {
|
||||
fprintf(stderr, "row %d: source write failed\n", i);
|
||||
rowfail = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/ww build -I %s/lib -o %s %s",
|
||||
bin, cwd, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row %d: build failed\n src: %s\n",
|
||||
i, rows[i].src);
|
||||
rowfail = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n",
|
||||
i, got, rows[i].want_exit, rows[i].src);
|
||||
rowfail = 1;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
{
|
||||
int cleanfail = 0;
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s/out.sepwork", tmpdir);
|
||||
if (runwait(cmd) != 0) cleanfail = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanfail = 1;
|
||||
if (cleanfail) {
|
||||
fprintf(stderr, "row %d: temporary cleanup failed\n", i);
|
||||
rowfail = 1;
|
||||
}
|
||||
}
|
||||
if (rowfail) fail++;
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d floats tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("floats: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* 958_types_sizelim_run — runtime proof of the lib/types SIZE_* and
|
||||
* UINTPTR_* limit constants (faithful port of ref/hare/types/arch+
|
||||
* x86_64.ha:16-26). Asserts both the values (MIN==0, MAX==U64_MAX) and
|
||||
* that the consts are usable as their declared type in arithmetic and an
|
||||
* i32 cast.
|
||||
*
|
||||
* SIZE_MAX is the no-cast def `def SIZE_MAX: size = U64_MAX;` — size is in
|
||||
* the unsigned class and 8B-wide on amd64, so the u64→size init coerces
|
||||
* without a cast (#113). UINTPTR_MAX keeps Hare's explicit cast
|
||||
* (`U64_MAX: uintptr`) because uintptr is outside the unsigned class. A
|
||||
* green row IS the proof both forms compiled and carry the right value.
|
||||
*
|
||||
* INT_MIN/MAX + UINT_MIN/MAX are deferred to #114 (ww int=8B vs Hare's 4B
|
||||
* on amd64 makes the value question open); RUNE_MAX is deferred to #112
|
||||
* (no \U rune-escape lexer yet). Only the 4 SIZE/UINTPTR consts land here.
|
||||
*
|
||||
* Table-driven like 957_size_type_run / 952_floats_run: each row is a
|
||||
* self-contained ww program; the cstage `ww build -I lib` compiles it, we
|
||||
* run the binary and assert the exit code. cstage-only by design (mirrors
|
||||
* 951/952/957): per-program wwstage byte-id is the 990-997 gates' job.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct row { const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* size limits: SIZE_MIN is zero; SIZE_MAX equals U64_MAX both by
|
||||
* direct compare and by the all-ones wrap (MAX + 1 == 0 on 8B). */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let lo: size = types.SIZE_MIN;\n"
|
||||
" if (lo != 0) { return 1; };\n"
|
||||
" let hi: size = types.SIZE_MAX;\n"
|
||||
" if (hi != (types.U64_MAX): size) { return 2; };\n"
|
||||
" if (hi + 1 != 0) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* SIZE_MAX usable in size arithmetic, propagated through an i32
|
||||
* cast: (2^64-1) + 43 wraps to 42. */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let hi: size = types.SIZE_MAX;\n"
|
||||
" let v: size = hi + 43;\n"
|
||||
" return v: i32;\n"
|
||||
"};\n", 42 },
|
||||
/* uintptr limits: UINTPTR_MIN is zero; UINTPTR_MAX equals U64_MAX
|
||||
* (Hare's explicit-cast def) both by compare and by the wrap. */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let lo: uintptr = types.UINTPTR_MIN;\n"
|
||||
" if (lo != 0) { return 1; };\n"
|
||||
" let hi: uintptr = types.UINTPTR_MAX;\n"
|
||||
" if (hi != (types.U64_MAX): uintptr) { return 2; };\n"
|
||||
" if (hi + 1 != 0) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* UINTPTR_MAX usable in uintptr arithmetic, propagated through an
|
||||
* i32 cast: (2^64-1) + 43 wraps to 42. */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let hi: uintptr = types.UINTPTR_MAX;\n"
|
||||
" let v: uintptr = hi + 43;\n"
|
||||
" return v: i32;\n"
|
||||
"};\n", 42 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char tmpdir[64] = "/tmp/wwslim_XXXXXX";
|
||||
if (mkdtemp(tmpdir) == NULL) {
|
||||
perror("types_sizelim: mkdtemp");
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
char src[128], outbin[128], sepwork[160], rmcmd[192];
|
||||
snprintf(src, sizeof src, "%s/wwslim_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
|
||||
snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; goto cleanup; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "%s/ww build -I %s/lib -o %s %s",
|
||||
bin, cwd, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row %d: build failed\n src: %s\n",
|
||||
i, rows[i].src);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n",
|
||||
i, got, rows[i].want_exit, rows[i].src);
|
||||
fail++;
|
||||
}
|
||||
|
||||
cleanup: {
|
||||
int cleanbad = 0;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1;
|
||||
if (runwait(rmcmd) != 0) cleanbad = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanbad = 1;
|
||||
if (cleanbad) {
|
||||
fprintf(stderr, "row %d: temporary cleanup failed\n", i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d types_sizelim tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("types_sizelim: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* 959_types_intlim_run — runtime proof of the lib/types INT_* and UINT_*
|
||||
* limit constants. ww's int/uint are machine-word (8B on amd64, type.c:58),
|
||||
* so the limits are DERIVED from size(int) (Go-style, cf math.MaxInt) rather
|
||||
* than aliased to a per-arch literal the way Hare does (arch+x86_64.ha maps
|
||||
* INT_MAX→I32_MAX). On amd64 the derived forms must therefore equal the 64-bit
|
||||
* limits: INT_MAX==I64_MAX, INT_MIN==I64_MIN, UINT_MAX==U64_MAX, UINT_MIN==0
|
||||
* (#114, user ruling).
|
||||
*
|
||||
* Each row asserts both the value (vs the literal AND vs the i64/u64 limit
|
||||
* const) and usability — the derived def survives const-fold, carries the
|
||||
* right machine-word width, and is usable in arithmetic that wraps through an
|
||||
* i32 cast (INT_MAX+43 and UINT_MAX+43 each wrap to 42 in the low 32 bits).
|
||||
*
|
||||
* Table-driven like 958_types_sizelim_run: each row is a self-contained ww
|
||||
* program; the cstage `ww build -I lib` compiles it, we run the binary and
|
||||
* assert the exit code. cstage-only by design (mirrors 951/952/957/958):
|
||||
* per-program wwstage byte-id is the 990-997 gates' job.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct row { const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* int limits: INT_MIN is I64_MIN, INT_MAX is I64_MAX — both by
|
||||
* direct literal compare and against the i64 limit const. */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let lo: int = types.INT_MIN;\n"
|
||||
" if (lo != -9223372036854775808) { return 1; };\n"
|
||||
" if (lo: i64 != types.I64_MIN) { return 2; };\n"
|
||||
" let hi: int = types.INT_MAX;\n"
|
||||
" if (hi != 9223372036854775807) { return 3; };\n"
|
||||
" if (hi: i64 != types.I64_MAX) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* INT_MAX usable in int arithmetic, propagated through an i32 cast:
|
||||
* INT_MAX + 43 wraps past INT_MIN; low 32 bits are 42. */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let hi: int = types.INT_MAX;\n"
|
||||
" let v: int = hi + 43;\n"
|
||||
" return v: i32;\n"
|
||||
"};\n", 42 },
|
||||
/* uint limits: UINT_MIN is zero; UINT_MAX is U64_MAX — both by
|
||||
* compare and by the all-ones wrap (MAX + 1 == 0 on 8B). */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let lo: uint = types.UINT_MIN;\n"
|
||||
" if (lo != 0) { return 1; };\n"
|
||||
" let hi: uint = types.UINT_MAX;\n"
|
||||
" if (hi != 18446744073709551615) { return 2; };\n"
|
||||
" if (hi: u64 != types.U64_MAX) { return 3; };\n"
|
||||
" if (hi + 1 != 0) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* UINT_MAX usable in uint arithmetic, propagated through an i32
|
||||
* cast: (2^64-1) + 43 wraps to 42. */
|
||||
{ "package main;\n"
|
||||
"import types;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let hi: uint = types.UINT_MAX;\n"
|
||||
" let v: uint = hi + 43;\n"
|
||||
" return v: i32;\n"
|
||||
"};\n", 42 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char tmpdir[64] = "/tmp/wwilim_XXXXXX";
|
||||
if (mkdtemp(tmpdir) == NULL) {
|
||||
perror("types_intlim: mkdtemp");
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
char src[128], outbin[128], sepwork[160], rmcmd[192];
|
||||
snprintf(src, sizeof src, "%s/wwilim_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
|
||||
snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; goto cleanup; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "%s/ww build -I %s/lib -o %s %s",
|
||||
bin, cwd, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row %d: build failed\n src: %s\n",
|
||||
i, rows[i].src);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n",
|
||||
i, got, rows[i].want_exit, rows[i].src);
|
||||
fail++;
|
||||
}
|
||||
|
||||
cleanup: {
|
||||
int cleanbad = 0;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1;
|
||||
if (runwait(rmcmd) != 0) cleanbad = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanbad = 1;
|
||||
if (cleanbad) {
|
||||
fprintf(stderr, "row %d: temporary cleanup failed\n", i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d types_intlim tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("types_intlim: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/*
|
||||
* 963_sort_run — runtime proof of lib/sort's search + lbisect + rbisect
|
||||
* (faithful port of ref/hare/sort/{search,bisect}.ha). The three rows
|
||||
* mirror the three relevant @test fns in ref/hare/sort/+test.ha
|
||||
* (search:42-51, lbisect:8-23, rbisect:25-40), instantiated on a
|
||||
* concrete []i32 with a real cmpfunc.
|
||||
*
|
||||
* lib/sort rides the opaque type-erasure surface proven by 962
|
||||
* (slice->[]opaque, *opaque<->*u8 reinterpret, uintptr byte stride). The
|
||||
* functions are exercised only here — they are dead in the bootstrap, so
|
||||
* no 990-997 gate touches them; this executed-and-checked probe is their
|
||||
* coverage (per the bootstrap-coverage discipline).
|
||||
*
|
||||
* The comparator binds its derefs to locals (`let va = *pa; if (va <
|
||||
* vb)`) rather than Hare's inline `if (*pa < *pb)`: the inline-deref-in-
|
||||
* comparison shape is mis-compiled by a PRE-EXISTING cgen bug (#116 /
|
||||
* task #18, the same family 962's header documents). The bug is in the
|
||||
* user-supplied comparator, NOT in search/bisect — those bind `r =
|
||||
* cmp(...)` then compare, so the lib port is faithful and unaffected.
|
||||
*
|
||||
* Table-driven like 958_types_sizelim_run / 952_floats_run: each row is a
|
||||
* self-contained ww program `import sort;`. cstage `ww build -I lib`
|
||||
* compiles it, we run the binary and assert the exit code (0 == every
|
||||
* assertion held; a small locator otherwise). cstage-only by design
|
||||
* (mirrors 958/962): lib/sort is not compiler-imported, so per-program
|
||||
* wwstage byte-id is out of scope and the 990-997 gates are unaffected.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define INTS \
|
||||
"fn ints(a: *opaque, b: *opaque) int = {\n" \
|
||||
" let pa: *i32 = a: *i32;\n" \
|
||||
" let pb: *i32 = b: *i32;\n" \
|
||||
" let va: i32 = *pa;\n" \
|
||||
" let vb: i32 = *pb;\n" \
|
||||
" if (va < vb) { return -1; };\n" \
|
||||
" if (va > vb) { return 1; };\n" \
|
||||
" return 0;\n" \
|
||||
"};\n"
|
||||
|
||||
struct row { const char *label; const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* search: every present key resolves to its index; an absent key
|
||||
* is void. ref/hare/sort/+test.ha:42-51. */
|
||||
{ "search",
|
||||
"package main;\n"
|
||||
"import sort;\n"
|
||||
INTS
|
||||
"export fn main() i32 = {\n"
|
||||
" let nums: [10]i32 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n"
|
||||
" let s: []i32 = nums[0:10];\n"
|
||||
" let n: size = len(s): size;\n"
|
||||
" let i: size = 0;\n"
|
||||
" for (i < n) {\n"
|
||||
" let key: i32 = nums[i];\n"
|
||||
" let p: size = sort.search(s, size(i32), &key, ints) as size;\n"
|
||||
" if (p != i) { return 1; };\n"
|
||||
" i = i + 1;\n"
|
||||
" };\n"
|
||||
" let miss: i32 = 1337;\n"
|
||||
" let r = sort.search(s, size(i32), &miss, ints);\n"
|
||||
" if (!(r is void)) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* lbisect: first-occurrence index for present values; insertion
|
||||
* point for absent ones. ref/hare/sort/+test.ha:8-23. */
|
||||
{ "lbisect",
|
||||
"package main;\n"
|
||||
"import sort;\n"
|
||||
INTS
|
||||
"export fn main() i32 = {\n"
|
||||
" let nums: [10]i32 = [1, 3, 4, 4, 5, 7, 9, 11, 11, 11];\n"
|
||||
" let s: []i32 = nums[0:10];\n"
|
||||
" let n: size = len(s): size;\n"
|
||||
" let i: size = 0;\n"
|
||||
" for (i < n) {\n"
|
||||
" if (i == 0 || nums[i - 1] != nums[i]) {\n"
|
||||
" let key: i32 = nums[i];\n"
|
||||
" if (sort.lbisect(s, size(i32), &key, ints) != i) { return 1; };\n"
|
||||
" };\n"
|
||||
" i = i + 1;\n"
|
||||
" };\n"
|
||||
" let n0: i32 = 0; if (sort.lbisect(s, size(i32), &n0, ints) != 0) { return 2; };\n"
|
||||
" let n6: i32 = 6; if (sort.lbisect(s, size(i32), &n6, ints) != 5) { return 3; };\n"
|
||||
" let n8: i32 = 8; if (sort.lbisect(s, size(i32), &n8, ints) != 6) { return 4; };\n"
|
||||
" let n12: i32 = 12; if (sort.lbisect(s, size(i32), &n12, ints) != n) { return 5; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* rbisect: last-occurrence+1 index for present values; insertion
|
||||
* point for absent ones. ref/hare/sort/+test.ha:25-40. */
|
||||
{ "rbisect",
|
||||
"package main;\n"
|
||||
"import sort;\n"
|
||||
INTS
|
||||
"export fn main() i32 = {\n"
|
||||
" let nums: [10]i32 = [1, 3, 4, 4, 5, 7, 9, 11, 11, 11];\n"
|
||||
" let s: []i32 = nums[0:10];\n"
|
||||
" let n: size = len(s): size;\n"
|
||||
" let i: size = 0;\n"
|
||||
" for (i < n) {\n"
|
||||
" if (i == n - 1 || nums[i + 1] != nums[i]) {\n"
|
||||
" let key: i32 = nums[i];\n"
|
||||
" if (sort.rbisect(s, size(i32), &key, ints) != i + 1) { return 1; };\n"
|
||||
" };\n"
|
||||
" i = i + 1;\n"
|
||||
" };\n"
|
||||
" let n0: i32 = 0; if (sort.rbisect(s, size(i32), &n0, ints) != 0) { return 2; };\n"
|
||||
" let n6: i32 = 6; if (sort.rbisect(s, size(i32), &n6, ints) != 5) { return 3; };\n"
|
||||
" let n8: i32 = 8; if (sort.rbisect(s, size(i32), &n8, ints) != 6) { return 4; };\n"
|
||||
" let n12: i32 = 12; if (sort.rbisect(s, size(i32), &n12, ints) != n) { return 5; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
{ NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char tmpdir[64] = "/tmp/wwsort_XXXXXX";
|
||||
if (mkdtemp(tmpdir) == NULL) {
|
||||
perror("sort: mkdtemp");
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
char src[128], outbin[128], sepwork[160], rmcmd[192];
|
||||
snprintf(src, sizeof src, "%s/wwsort_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
|
||||
snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; goto cleanup; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "%s/ww build -I %s/lib -o %s %s",
|
||||
bin, cwd, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build failed\n", rows[i].label);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row[%s]: exit %d, want %d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
|
||||
cleanup: {
|
||||
int cleanbad = 0;
|
||||
if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1;
|
||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1;
|
||||
if (runwait(rmcmd) != 0) cleanbad = 1;
|
||||
if (rmdir(tmpdir) != 0) cleanbad = 1;
|
||||
if (cleanbad) {
|
||||
fprintf(stderr, "row[%s]: temporary cleanup failed\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d sort tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("sort: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
8
test/wcc/data/r682_enum_frame_smash/case.ww
Normal file
8
test/wcc/data/r682_enum_frame_smash/case.ww
Normal file
@@ -0,0 +1,8 @@
|
||||
//ww:run-exit 5
|
||||
package main;
|
||||
type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32,
|
||||
D = 3i32, E = 11i32, F = 5i32 };
|
||||
fn main() i32 = {
|
||||
let a: [6]tk = [tk.A, tk.B, tk.C, tk.D, tk.E, tk.F];
|
||||
return a[5]: i32;
|
||||
};
|
||||
7
test/wcc/data/r682_enum_global_idx0/case.ww
Normal file
7
test/wcc/data/r682_enum_global_idx0/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };
|
||||
let g: [4]tk = [tk.A, tk.B, tk.C, tk.D];
|
||||
fn main() i32 = {
|
||||
return g[0]: i32;
|
||||
};
|
||||
7
test/wcc/data/r682_enum_global_read/case.ww
Normal file
7
test/wcc/data/r682_enum_global_read/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 99
|
||||
package main;
|
||||
type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };
|
||||
let g: [4]tk = [tk.A, tk.B, tk.C, tk.D];
|
||||
fn main() i32 = {
|
||||
return g[2]: i32;
|
||||
};
|
||||
7
test/wcc/data/r682_enum_local_init_sum/case.ww
Normal file
7
test/wcc/data/r682_enum_local_init_sum/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 10
|
||||
package main;
|
||||
type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };
|
||||
fn main() i32 = {
|
||||
let a: [4]tk = [tk.A, tk.B, tk.C, tk.D];
|
||||
return (a[0]: i32) + (a[3]: i32);
|
||||
};
|
||||
7
test/wcc/data/r682_enum_local_read/case.ww
Normal file
7
test/wcc/data/r682_enum_local_read/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 21
|
||||
package main;
|
||||
type tk = enum i32 { A = 7i32, B = 21i32, C = 99i32, D = 3i32 };
|
||||
fn main() i32 = {
|
||||
let a: [4]tk = [tk.A, tk.B, tk.C, tk.D];
|
||||
return a[1]: i32;
|
||||
};
|
||||
7
test/wcc/data/r682_enum_local_signed/case.ww
Normal file
7
test/wcc/data/r682_enum_local_signed/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 251
|
||||
package main;
|
||||
type tk = enum i32 { A = 7i32, G = -5i32 };
|
||||
fn main() i32 = {
|
||||
let a: [2]tk = [tk.G, tk.A];
|
||||
return a[0]: i32;
|
||||
};
|
||||
7
test/wcc/data/r682_enum_width1_u8/case.ww
Normal file
7
test/wcc/data/r682_enum_width1_u8/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 200
|
||||
package main;
|
||||
type tu = enum u8 { A = 7u8, B = 200u8, C = 3u8 };
|
||||
fn main() i32 = {
|
||||
let a: [3]tu = [tu.A, tu.B, tu.C];
|
||||
return a[1]: i32;
|
||||
};
|
||||
7
test/wcc/data/r682_enum_width2_i16/case.ww
Normal file
7
test/wcc/data/r682_enum_width2_i16/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 251
|
||||
package main;
|
||||
type ts = enum i16 { A = 7i16, G = -5i16, C = 99i16 };
|
||||
fn main() i32 = {
|
||||
let a: [3]ts = [ts.A, ts.G, ts.C];
|
||||
return a[1]: i32;
|
||||
};
|
||||
10
test/wcc/data/r683_slice_cap_copy/case.ww
Normal file
10
test/wcc/data/r683_slice_cap_copy/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;
|
||||
let b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;
|
||||
let t: [2][]u8 = [a, b];
|
||||
let q: []u8 = t[0];
|
||||
return q.cap: i32;
|
||||
};
|
||||
9
test/wcc/data/r683_slice_cap_direct0/case.ww
Normal file
9
test/wcc/data/r683_slice_cap_direct0/case.ww
Normal file
@@ -0,0 +1,9 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;
|
||||
let b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;
|
||||
let t: [2][]u8 = [a, b];
|
||||
return t[0].cap: i32;
|
||||
};
|
||||
9
test/wcc/data/r683_slice_cap_direct1/case.ww
Normal file
9
test/wcc/data/r683_slice_cap_direct1/case.ww
Normal file
@@ -0,0 +1,9 @@
|
||||
//ww:run-exit 6
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;
|
||||
let b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;
|
||||
let t: [2][]u8 = [a, b];
|
||||
return t[1].cap: i32;
|
||||
};
|
||||
9
test/wcc/data/r683_slice_len0/case.ww
Normal file
9
test/wcc/data/r683_slice_len0/case.ww
Normal file
@@ -0,0 +1,9 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;
|
||||
let b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 8;
|
||||
let t: [2][]u8 = [a, b];
|
||||
return t[0].len: i32;
|
||||
};
|
||||
9
test/wcc/data/r683_slice_len1/case.ww
Normal file
9
test/wcc/data/r683_slice_len1/case.ww
Normal file
@@ -0,0 +1,9 @@
|
||||
//ww:run-exit 2
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;
|
||||
let b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 8;
|
||||
let t: [2][]u8 = [a, b];
|
||||
return t[1].len: i32;
|
||||
};
|
||||
10
test/wcc/data/r683_slice_ptr/case.ww
Normal file
10
test/wcc/data/r683_slice_ptr/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 4
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8; hb[0] = 9u8; hb[1] = 4u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;
|
||||
let b: []u8; b.ptr = &hb[1]; b.len = 2; b.cap = 8;
|
||||
let t: [2][]u8 = [a, b];
|
||||
let q: []u8 = t[1];
|
||||
return q[0]: i32;
|
||||
};
|
||||
10
test/wcc/data/r683_slice_stride3/case.ww
Normal file
10
test/wcc/data/r683_slice_stride3/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let hb: [8]u8;
|
||||
let a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;
|
||||
let b: []u8; b.ptr = &hb[0]; b.len = 5; b.cap = 8;
|
||||
let c: []u8; c.ptr = &hb[0]; c.len = 7; c.cap = 8;
|
||||
let t: [3][]u8 = [a, b, c];
|
||||
return t[2].len: i32;
|
||||
};
|
||||
9
test/wcc/data/r683_str_cap_copy/case.ww
Normal file
9
test/wcc/data/r683_str_cap_copy/case.ww
Normal file
@@ -0,0 +1,9 @@
|
||||
//ww:run-exit 5
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let a: str = "abcde";
|
||||
let b: str = "xy";
|
||||
let t: [2]str = [a, b];
|
||||
let q: str = t[0];
|
||||
return q.cap: i32;
|
||||
};
|
||||
8
test/wcc/data/r683_str_cap_direct0/case.ww
Normal file
8
test/wcc/data/r683_str_cap_direct0/case.ww
Normal file
@@ -0,0 +1,8 @@
|
||||
//ww:run-exit 5
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let a: str = "abcde";
|
||||
let b: str = "xy";
|
||||
let t: [2]str = [a, b];
|
||||
return t[0].cap: i32;
|
||||
};
|
||||
8
test/wcc/data/r683_str_cap_direct1/case.ww
Normal file
8
test/wcc/data/r683_str_cap_direct1/case.ww
Normal file
@@ -0,0 +1,8 @@
|
||||
//ww:run-exit 2
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let a: str = "abcde";
|
||||
let b: str = "xy";
|
||||
let t: [2]str = [a, b];
|
||||
return t[1].cap: i32;
|
||||
};
|
||||
8
test/wcc/data/r683_str_len1/case.ww
Normal file
8
test/wcc/data/r683_str_len1/case.ww
Normal file
@@ -0,0 +1,8 @@
|
||||
//ww:run-exit 2
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let a: str = "abc";
|
||||
let b: str = "de";
|
||||
let t: [2]str = [a, b];
|
||||
return t[1].len: i32;
|
||||
};
|
||||
5
test/wcc/data/r683_str_lit_cap_symmetry/case.ww
Normal file
5
test/wcc/data/r683_str_lit_cap_symmetry/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:compile
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
return "abc".cap: i32;
|
||||
};
|
||||
7
test/wcc/data/r683_struct_elem/case.ww
Normal file
7
test/wcc/data/r683_struct_elem/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 4
|
||||
package main;
|
||||
type Pt = struct { x: i32, y: i32 };
|
||||
export fn main() i32 = {
|
||||
let t: [2]Pt = [Pt { x = 1, y = 2 }, Pt { x = 3, y = 4 }];
|
||||
return t[1].y: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_local_int_elem/case.ww
Normal file
6
test/wcc/data/r684_local_int_elem/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 30
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]int = [10, 20, 30, 40];
|
||||
return x[2]: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_local_one_len/case.ww
Normal file
6
test/wcc/data/r684_local_one_len/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 1
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]int = [7];
|
||||
return x.len: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_local_str_elem/case.ww
Normal file
6
test/wcc/data/r684_local_str_elem/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 2
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]str = ["ab", "cde"];
|
||||
return x[0].len: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_local_str_len/case.ww
Normal file
6
test/wcc/data/r684_local_str_len/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]str = ["a", "b", "c"];
|
||||
return x.len: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_local_u8_elem/case.ww
Normal file
6
test/wcc/data/r684_local_u8_elem/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 5
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];
|
||||
return x[4]: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_local_u8_len/case.ww
Normal file
6
test/wcc/data/r684_local_u8_len/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 5
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];
|
||||
return x.len: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_mod_int_elem/case.ww
Normal file
6
test/wcc/data/r684_mod_int_elem/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 30
|
||||
package main;
|
||||
let x: [_]int = [10, 20, 30, 40];
|
||||
export fn main() i32 = {
|
||||
return x[2]: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_mod_one_elem/case.ww
Normal file
6
test/wcc/data/r684_mod_one_elem/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
let x: [_]int = [7];
|
||||
export fn main() i32 = {
|
||||
return x[0]: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_mod_one_len/case.ww
Normal file
6
test/wcc/data/r684_mod_one_len/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 1
|
||||
package main;
|
||||
let x: [_]int = [7];
|
||||
export fn main() i32 = {
|
||||
return x.len: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_mod_str_len/case.ww
Normal file
6
test/wcc/data/r684_mod_str_len/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
let x: [_]str = ["a", "b", "c"];
|
||||
export fn main() i32 = {
|
||||
return x.len: i32;
|
||||
};
|
||||
6
test/wcc/data/r684_noinit_local_reject/case.ww
Normal file
6
test/wcc/data/r684_noinit_local_reject/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:error "[_]T needs an array-literal initialiser"
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]str;
|
||||
return 0;
|
||||
};
|
||||
4
test/wcc/data/r684_noinit_mod_reject/case.ww
Normal file
4
test/wcc/data/r684_noinit_mod_reject/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:error "[_]T needs an array-literal initialiser"
|
||||
package main;
|
||||
let x: [_]int;
|
||||
export fn main() i32 = { return 0; };
|
||||
6
test/wcc/data/r684_nonarray_local_reject/case.ww
Normal file
6
test/wcc/data/r684_nonarray_local_reject/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:error "[_]T needs an array-literal initialiser"
|
||||
package main;
|
||||
export fn main() i32 = {
|
||||
let x: [_]int = 5;
|
||||
return 0;
|
||||
};
|
||||
10
test/wcc/data/r685_tagged_bool_variant/case.ww
Normal file
10
test/wcc/data/r685_tagged_bool_variant/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 1
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let t: [2]cell = [5i64, true];
|
||||
match (t[1]) {
|
||||
case let v: i64 => { return -1; };
|
||||
case let z: bool => { if (z) { return 1; }; return 0; };
|
||||
};
|
||||
};
|
||||
16
test/wcc/data/r685_tagged_boxed_void/case.ww
Normal file
16
test/wcc/data/r685_tagged_boxed_void/case.ww
Normal file
@@ -0,0 +1,16 @@
|
||||
//ww:run-exit 13
|
||||
package main;
|
||||
type ov = (i32 | void);
|
||||
export fn main() i32 = {
|
||||
let t: [3]ov = [1i32, 2i32, void];
|
||||
let sum: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < 3) {
|
||||
match (t[i]) {
|
||||
case let v: i32 => { sum = sum + v; };
|
||||
case void => { sum = sum + 10; };
|
||||
};
|
||||
i = i + 1;
|
||||
};
|
||||
return sum;
|
||||
};
|
||||
10
test/wcc/data/r685_tagged_lit_first/case.ww
Normal file
10
test/wcc/data/r685_tagged_lit_first/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 99
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let t: [2]cell = [99i64, 7i64];
|
||||
match (t[0]) {
|
||||
case let v: i64 => { return v: i32; };
|
||||
case let z: bool => { return -1; };
|
||||
};
|
||||
};
|
||||
10
test/wcc/data/r685_tagged_lit_second/case.ww
Normal file
10
test/wcc/data/r685_tagged_lit_second/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let t: [2]cell = [99i64, 7i64];
|
||||
match (t[1]) {
|
||||
case let v: i64 => { return v: i32; };
|
||||
case let z: bool => { return -1; };
|
||||
};
|
||||
};
|
||||
16
test/wcc/data/r685_tagged_lit_sum106/case.ww
Normal file
16
test/wcc/data/r685_tagged_lit_sum106/case.ww
Normal file
@@ -0,0 +1,16 @@
|
||||
//ww:run-exit 106
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let t: [2]cell = [99i64, 7i64];
|
||||
let sum: i64 = 0i64;
|
||||
let i: i32 = 0;
|
||||
for (i < 2) {
|
||||
match (t[i]) {
|
||||
case let v: i64 => { sum = sum + v; };
|
||||
case let z: bool => { sum = sum + 1i64; };
|
||||
};
|
||||
i = i + 1;
|
||||
};
|
||||
return sum: i32;
|
||||
};
|
||||
7
test/wcc/data/r685_tagged_repeat_reject/case.ww
Normal file
7
test/wcc/data/r685_tagged_repeat_reject/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:error "repeat of a tagged-union array-literal element not wired"
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let t: [3]cell = [99i64...];
|
||||
return 0;
|
||||
};
|
||||
10
test/wcc/data/r685_tagged_stride3/case.ww
Normal file
10
test/wcc/data/r685_tagged_stride3/case.ww
Normal file
@@ -0,0 +1,10 @@
|
||||
//ww:run-exit 30
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let t: [3]cell = [10i64, 20i64, 30i64];
|
||||
match (t[2]) {
|
||||
case let v: i64 => { return v: i32; };
|
||||
case let z: bool => { return -1; };
|
||||
};
|
||||
};
|
||||
18
test/wcc/data/r685_tagged_var_sum/case.ww
Normal file
18
test/wcc/data/r685_tagged_var_sum/case.ww
Normal file
@@ -0,0 +1,18 @@
|
||||
//ww:run-exit 42
|
||||
package main;
|
||||
type cell = (i64 | bool);
|
||||
export fn main() i32 = {
|
||||
let a: cell = 40i64;
|
||||
let b: cell = 2i64;
|
||||
let t: [2]cell = [a, b];
|
||||
let sum: i64 = 0i64;
|
||||
let i: i32 = 0;
|
||||
for (i < 2) {
|
||||
match (t[i]) {
|
||||
case let v: i64 => { sum = sum + v; };
|
||||
case let z: bool => { sum = sum + 100i64; };
|
||||
};
|
||||
i = i + 1;
|
||||
};
|
||||
return sum: i32;
|
||||
};
|
||||
4
test/wcc/data/r686_slice_glob_cap/case.ww
Normal file
4
test/wcc/data/r686_slice_glob_cap/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run
|
||||
package main;
|
||||
let g: []u8;
|
||||
export fn main() i32 = { return g.cap: i32; };
|
||||
4
test/wcc/data/r686_slice_glob_len/case.ww
Normal file
4
test/wcc/data/r686_slice_glob_len/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run
|
||||
package main;
|
||||
let g: []u8;
|
||||
export fn main() i32 = { return g.len: i32; };
|
||||
7
test/wcc/data/r686_slice_glob_ptr_null/case.ww
Normal file
7
test/wcc/data/r686_slice_glob_ptr_null/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
let g: []u8;
|
||||
export fn main() i32 = {
|
||||
if (g.ptr == nil) { return 7; };
|
||||
return 0;
|
||||
};
|
||||
4
test/wcc/data/r686_str_glob_len/case.ww
Normal file
4
test/wcc/data/r686_str_glob_len/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run
|
||||
package main;
|
||||
let s: str;
|
||||
export fn main() i32 = { return s.len: i32; };
|
||||
7
test/wcc/data/r686_str_glob_ptr_null/case.ww
Normal file
7
test/wcc/data/r686_str_glob_ptr_null/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 9
|
||||
package main;
|
||||
let s: str;
|
||||
export fn main() i32 = {
|
||||
if (s.ptr == nil) { return 9; };
|
||||
return 0;
|
||||
};
|
||||
5
test/wcc/data/r687_alias_slice/case.ww
Normal file
5
test/wcc/data/r687_alias_slice/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 7
|
||||
package main;
|
||||
type S = []u8;
|
||||
let g: S = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = { return (g[0]:i32)+(g.len:i32)+(g.cap:i32); };
|
||||
4
test/wcc/data/r687_i32_sum/case.ww
Normal file
4
test/wcc/data/r687_i32_sum/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 24
|
||||
package main;
|
||||
let g: []i32 = [7i32, 8i32, 9i32];
|
||||
export fn main() i32 = { return (g[0]:i32)+(g[1]:i32)+(g[2]:i32); };
|
||||
4
test/wcc/data/r687_i64_e1/case.ww
Normal file
4
test/wcc/data/r687_i64_e1/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 20
|
||||
package main;
|
||||
let g: []i64 = [10i64, 20i64, 30i64];
|
||||
export fn main() i32 = { return g[1]: i32; };
|
||||
4
test/wcc/data/r687_i64_lencap/case.ww
Normal file
4
test/wcc/data/r687_i64_lencap/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 6
|
||||
package main;
|
||||
let g: []i64 = [10i64, 20i64, 30i64];
|
||||
export fn main() i32 = { return (g.len:i32)+(g.cap:i32); };
|
||||
4
test/wcc/data/r687_neg_def_slicelit/case.ww
Normal file
4
test/wcc/data/r687_neg_def_slicelit/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:error "module-level slice-literal init needs a writable `let`"
|
||||
package main;
|
||||
def g: []u8 = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = { return g.len: i32; };
|
||||
4
test/wcc/data/r687_neg_repeat_slicelit/case.ww
Normal file
4
test/wcc/data/r687_neg_repeat_slicelit/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:error "'...' repeat has no target length in a slice literal"
|
||||
package main;
|
||||
let g: []u8 = [1u8, 2u8...];
|
||||
export fn main() i32 = { return g.len: i32; };
|
||||
4
test/wcc/data/r687_neg_str_slicelit/case.ww
Normal file
4
test/wcc/data/r687_neg_str_slicelit/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:error "slice-of-{str,slice,tagged} literal static-init unsupported"
|
||||
package main;
|
||||
let g: []str = ["a", "b"];
|
||||
export fn main() i32 = { return g.len: i32; };
|
||||
4
test/wcc/data/r687_one_edge/case.ww
Normal file
4
test/wcc/data/r687_one_edge/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 43
|
||||
package main;
|
||||
let g: []u8 = [42u8];
|
||||
export fn main() i32 = { return (g[0]:i32)+(g.len:i32); };
|
||||
7
test/wcc/data/r687_struct_3f/case.ww
Normal file
7
test/wcc/data/r687_struct_3f/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 23
|
||||
package main;
|
||||
type q = struct { a: u8, b: i64, c: i32 };
|
||||
let g: []q = [q{a=1u8, b=2i64, c=3i32}, q{a=4u8, b=5i64, c=6i32}];
|
||||
export fn main() i32 = {
|
||||
return (g[0].a:i32)+(g[0].b:i32)+(g[0].c:i32)+(g[1].a:i32)+(g[1].b:i32)+(g[1].c:i32)+(g.len:i32);
|
||||
};
|
||||
8
test/wcc/data/r687_struct_arrvar_local/case.ww
Normal file
8
test/wcc/data/r687_struct_arrvar_local/case.ww
Normal file
@@ -0,0 +1,8 @@
|
||||
//ww:run-exit 14
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
export fn main() i32 = {
|
||||
let arr: [2]pt = [pt{a=1i64, b=2i64}, pt{a=3i64, b=4i64}];
|
||||
let g: []pt = arr;
|
||||
return (g[0].a:i32)+(g[0].b:i32)+(g[1].a:i32)+(g[1].b:i32)+(g.len:i32)+(g.cap:i32);
|
||||
};
|
||||
7
test/wcc/data/r687_struct_one/case.ww
Normal file
7
test/wcc/data/r687_struct_one/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 13
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
let g: []pt = [pt{a=5i64, b=6i64}];
|
||||
export fn main() i32 = {
|
||||
return (g[0].a:i32)+(g[0].b:i32)+(g.len:i32)+(g.cap:i32);
|
||||
};
|
||||
7
test/wcc/data/r687_struct_pt/case.ww
Normal file
7
test/wcc/data/r687_struct_pt/case.ww
Normal file
@@ -0,0 +1,7 @@
|
||||
//ww:run-exit 14
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
let g: []pt = [pt{a=1i64, b=2i64}, pt{a=3i64, b=4i64}];
|
||||
export fn main() i32 = {
|
||||
return (g[0].a:i32)+(g[0].b:i32)+(g[1].a:i32)+(g[1].b:i32)+(g.len:i32)+(g.cap:i32);
|
||||
};
|
||||
4
test/wcc/data/r687_u8_cap/case.ww
Normal file
4
test/wcc/data/r687_u8_cap/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
let g: []u8 = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = { return g.cap: i32; };
|
||||
4
test/wcc/data/r687_u8_e0/case.ww
Normal file
4
test/wcc/data/r687_u8_e0/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 1
|
||||
package main;
|
||||
let g: []u8 = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = { return g[0]: i32; };
|
||||
4
test/wcc/data/r687_u8_e2/case.ww
Normal file
4
test/wcc/data/r687_u8_e2/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
let g: []u8 = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = { return g[2]: i32; };
|
||||
4
test/wcc/data/r687_u8_len/case.ww
Normal file
4
test/wcc/data/r687_u8_len/case.ww
Normal file
@@ -0,0 +1,4 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
let g: []u8 = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = { return g.len: i32; };
|
||||
6
test/wcc/data/r687_u8_sum/case.ww
Normal file
6
test/wcc/data/r687_u8_sum/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 12
|
||||
package main;
|
||||
let g: []u8 = [1u8, 2u8, 3u8];
|
||||
export fn main() i32 = {
|
||||
return (g[0]:i32)+(g[1]:i32)+(g[2]:i32)+(g.len:i32)+(g.cap:i32);
|
||||
};
|
||||
5
test/wcc/data/r688_arr_a0/case.ww
Normal file
5
test/wcc/data/r688_arr_a0/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 1
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];
|
||||
export fn main() i32 = { return g[0].a: i32; };
|
||||
5
test/wcc/data/r688_arr_a1/case.ww
Normal file
5
test/wcc/data/r688_arr_a1/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];
|
||||
export fn main() i32 = { return g[1].a: i32; };
|
||||
5
test/wcc/data/r688_arr_b0/case.ww
Normal file
5
test/wcc/data/r688_arr_b0/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 2
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];
|
||||
export fn main() i32 = { return g[0].b: i32; };
|
||||
5
test/wcc/data/r688_arr_b1/case.ww
Normal file
5
test/wcc/data/r688_arr_b1/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 4
|
||||
package main;
|
||||
type pt = struct { a: i64, b: i64 };
|
||||
let g: [2]pt = [pt { a = 1, b = 2 }, pt { a = 3, b = 4 }];
|
||||
export fn main() i32 = { return g[1].b: i32; };
|
||||
5
test/wcc/data/r688_rec_tag1/case.ww
Normal file
5
test/wcc/data/r688_rec_tag1/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 2
|
||||
package main;
|
||||
type rec = struct { tag: u8, x: i32, y: i64 };
|
||||
let g: [2]rec = [rec { tag = 1u8, x = 100, y = 11 }, rec { tag = 2u8, x = 200, y = 22 }];
|
||||
export fn main() i32 = { return g[1].tag: i32; };
|
||||
5
test/wcc/data/r688_rec_x1/case.ww
Normal file
5
test/wcc/data/r688_rec_x1/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 200
|
||||
package main;
|
||||
type rec = struct { tag: u8, x: i32, y: i64 };
|
||||
let g: [2]rec = [rec { tag = 1u8, x = 100, y = 11 }, rec { tag = 2u8, x = 200, y = 22 }];
|
||||
export fn main() i32 = { return g[1].x: i32; };
|
||||
5
test/wcc/data/r688_rec_y1/case.ww
Normal file
5
test/wcc/data/r688_rec_y1/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 22
|
||||
package main;
|
||||
type rec = struct { tag: u8, x: i32, y: i64 };
|
||||
let g: [2]rec = [rec { tag = 1u8, x = 100, y = 11 }, rec { tag = 2u8, x = 200, y = 22 }];
|
||||
export fn main() i32 = { return g[1].y: i32; };
|
||||
6
test/wcc/data/r688_slice_x1_byteid/case.ww
Normal file
6
test/wcc/data/r688_slice_x1_byteid/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:compile
|
||||
package main;
|
||||
type rec = struct { tag: u8, x: i32, y: i64 };
|
||||
let arr: [2]rec = [rec { tag = 1u8, x = 100, y = 11 }, rec { tag = 2u8, x = 200, y = 22 }];
|
||||
let g: []rec = arr;
|
||||
export fn main() i32 = { return g[1].x: i32; };
|
||||
6
test/wcc/data/r689_gpr_chain_i64_sink/case.ww
Normal file
6
test/wcc/data/r689_gpr_chain_i64_sink/case.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
//ww:run-exit 1
|
||||
package main;
|
||||
type T = struct { q: i64 };
|
||||
type S = struct { x: T };
|
||||
let gp: *S = nil;
|
||||
export fn main() i32 = { let s: S = S{x=T{q=0}}; gp = &s; s.x.q = 4294967297; let v: i64 = gp.x.q; return (v >> 32): i32; };
|
||||
5
test/wcc/data/r689_gpr_chain_str_len/case.ww
Normal file
5
test/wcc/data/r689_gpr_chain_str_len/case.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
//ww:run-exit 3
|
||||
package main;
|
||||
type S = struct { a: i64, sf: str };
|
||||
let gp: *S = nil;
|
||||
export fn main() i32 = { let s: S = S{a=0,sf="abc"}; gp = &s; return gp.sf.len: i32; };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user