/* * 928_str_abi_run — end-to-end runtime coverage for the str->24B * {ptr,len,cap} 3-reg ABI (Commit #1 / Phase 3, str IS []u8). * * The scratch str-ABI probe matrix (task #3) only diffs asm byte-id; * byte-identity proves the two stages agree, NOT that the emitted code * is correct (a shared miscompile passes byte-id silently). This file * pins the *runtime* contract: build each fixture through both the * cstage `ww` and the wwstage `ww_ww` driver and confirm the program's * own assertions hold (exit 0). * * Covers the ABI dimensions that exercise the new cap word and the * AX/BX/CX value / AX:DX:CX:R8 tagged+tuple register layout: str * literal (cap=len), str arg, str return, str struct field, the * (i64,str) and (str,i64) tuple return shapes, deref-store `*p = s`, * and []str index write+read. Phase 2 collapse folds (str IS []u8): * str byte-index read `s[i]` (element stride via the type table, * commit a5ca21d) and str widened into a tagged-union variant (the * payload store folded onto the common slice arm, commit b416e71). * * Deliberately NOT covered here (known, separately-tracked gaps found * during Commit #1 review — both byte-identical across stages, so the * byte-id gates stay green): * - str-containing struct passed BY VALUE: now >16B, falls into the * general ">16B struct byval" limitation (a non-str 24B struct * byval mis-compiles the same way); not a str-specific defect. * (task #10) * - `.cap` VALUE of a top-level str GLOBAL reads 0, not len: the * str-literal global DATAW emits only the 16B {ptr,len} payload, * not the 24B header — byte-identical across stages, but the cap * word is never initialised. Distinct from the .cap field/global * link-error (task #11), which IS fixed: `.cap` on a str field * (stored value) and the global field-read now compile and agree. */ #include #include #include #include #include #include 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[] = { /* Literal: cap = len for a static literal (no spare storage), * and .cap on a LOCAL str reads back. The new third word. */ { "literal_len_cap", "export fn main() i32 = {\n" " let s: str = \"hello\";\n" " if (s.len: i32 != 5) { return 1; };\n" " if (s.cap: i32 != 5) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* Arg: str passed as a 3-word arg (ptr,len,cap), len read in * the callee. Both a let-bound str and a bare literal arg. */ { "arg_len", "fn slen(s: str) i32 = { return s.len: i32; };\n" "export fn main() i32 = {\n" " let s: str = \"hello\";\n" " if (slen(s) != 5) { return 1; };\n" " if (slen(\"hi\") != 2) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* Return: callee returns a str in AX/BX/CX (no AX:DX shuffle — * str returns exactly like a slice now). */ { "return_str", "fn greet() str = { return \"hello world\"; };\n" "export fn main() i32 = {\n" " let g: str = greet();\n" " if (g.len: i32 != 11) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* Struct field: store a str into a 3-word field, read .len and * .cap back (field-store routes the base through DX to dodge * CX=cap; the cap word is stored, so b.s.cap == len here). */ { "struct_field_store_load", "type box = struct { s: str, n: i32 };\n" "export fn main() i32 = {\n" " let b: box;\n" " b.s = \"abcd\";\n" " b.n = 7i32;\n" " if (b.s.len: i32 != 4) { return 1; };\n" " if (b.n != 7) { return 2; };\n" " if (b.s.cap: i32 != 4) { return 3; };\n" " return 0;\n" "};\n", 0 }, /* Tuple (i64, str) return: AX=scalar, DX=ptr, CX=len, R8=cap; * 32B receive slot. */ { "tuple_int_str", "fn pair() (i64, str) = { return (42i64, \"hello\"); };\n" "export fn main() i32 = {\n" " let n, s = pair();\n" " if (n: i32 != 42) { return 1; };\n" " if (s.len: i32 != 5) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* Tuple (str, i64) return: reversed order, registers keyed by * element type not position. */ { "tuple_str_int", "fn pair() (str, i64) = { return (\"hi\", 7i64); };\n" "export fn main() i32 = {\n" " let s, n = pair();\n" " if (s.len: i32 != 2) { return 1; };\n" " if (n: i32 != 7) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* Deref-store: `*p = s` writes all three words through the * pointer (cap stashed across the pointer eval). */ { "deref_store", "fn setit(p: *str, v: str) void = { *p = v; };\n" "export fn main() i32 = {\n" " let s: str = \"hello\";\n" " let d: str;\n" " setit(&d, s);\n" " if (d.len: i32 != 5) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* []str index write + read: the str-element store pushes * cap/len and writes 3 words; the read loads them back. Guards * the str-element gate against the slice=24B collision (#7/754, * write-side). */ { "index_write_read", "export fn main() i32 = {\n" " let xs: [2]str;\n" " xs[0] = \"hi\";\n" " xs[1] = \"abc\";\n" " if (xs[0].len: i32 != 2) { return 1; };\n" " if (xs[1].len: i32 != 3) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* str byte index read: `s[i]` strides by the u8 element size (1), * routed through the type table post-collapse (a5ca21d). Guards * the N_INDEX str-element stride against a slice-width (24B) * miscompute now that str shares the slice path. */ { "str_index_read", "export fn main() i32 = {\n" " let s: str = \"hello\";\n" " if (s[0]: i32 != 104) { return 1; };\n" " if (s[1]: i32 != 101) { return 2; };\n" " if (s[4]: i32 != 111) { return 3; };\n" " return 0;\n" "};\n", 0 }, /* str widened into a tagged-union variant: the payload store * folds onto the common slice arm (3-word ptr/len/cap @ * slot+8/+16/+24 + tag, b416e71). Exercises literal, str-variable * and a str returned from a fn into `(str | i64)`, plus the i64 * arm so the variant-tag selection is checked both ways. */ { "tagged_union_str_store", "type sv = (str | i64);\n" "fn wrap(s: str) sv = { return s; };\n" "export fn main() i32 = {\n" " let x: sv = \"hello\";\n" " match (x) {\n" " case let s: str => { if (s.len: i32 != 5) { return 1; }; };\n" " case let n: i64 => { return 2; };\n" " };\n" " let a: str = \"world\";\n" " let y: sv = a;\n" " match (y) {\n" " case let s: str => { if (s.len: i32 != 5) { return 3; }; };\n" " case let n: i64 => { return 4; };\n" " };\n" " let z: sv = wrap(\"abcd\");\n" " match (z) {\n" " case let s: str => { if (s.len: i32 != 4) { return 5; }; };\n" " case let n: i64 => { return 6; };\n" " };\n" " let w: sv = 42i64;\n" " match (w) {\n" " case let s: str => { return 7; };\n" " case let n: i64 => { if (n: i32 != 42) { return 8; }; };\n" " };\n" " return 0;\n" "};\n", 0 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[96], tmpdir[96], cmd[1024]; snprintf(src, sizeof src, "/tmp/strabi_run_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/strabi_run_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[160]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return got; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[640]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "str_abi_run: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; if (got != rows[i].want) { fprintf(stderr, "str_abi_run[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "str_abi_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("str_abi_run: %d/%d ok\n", total, total); return 0; }