/* * 630_let_global — top-level mutable `let` end-to-end through the * full Cstage pipeline (w6c → w6a → w6l). Each fixture is a tiny * .ww program that exercises one path: literal-init read, plain * assignment, compound assignment, and address-of. * * Exit code = the value the runtime feeds to `exit(2)`. main's * return value flows into DI via rt/start.s and ends up as the * shell's $?. */ #include #include #include #include #include "wwtestpkg.h" static int run_exit(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } static int build_and_run(const char *bin, const char *prog) { /* `ww run` builds with the driver (w6c → w6a → w6l + libwwrt.a) * and executes the result. The exit code propagates back as the * shell's $? so we just compare against the fixture's want. */ char src[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/wwt_lg_%d.ww", getpid()); FILE *f = fopen(src, "wb"); if (!f) return -1; wwtest_fputs(prog, f); fclose(f); snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, src); int rc = run_exit(cmd); unlink(src); return rc; } struct fixture { const char *label; const char *prog; int want; }; static const struct fixture fixtures[] = { { "read", "let counter: i32 = 42;\n" "fn main() i32 = { return counter; };\n", 42, }, { "assign", "let counter: i32 = 0;\n" "fn main() i32 = { counter = 99; return counter; };\n", 99, }, { "compound", "let counter: i32 = 1;\n" "fn inc() void = { counter += 1; };\n" "fn main() i32 = { inc(); inc(); inc(); return counter; };\n", 4, }, { "zero-init", /* No initialiser → zero-filled DATAW slot. */ "let counter: i32;\n" "fn main() i32 = { counter = 7; return counter; };\n", 7, }, { "addr-of", "let counter: i32 = 11;\n" "fn main() i32 = {\n" "\tlet p: *i32 = &counter;\n" "\t*p = 55;\n" "\treturn counter;\n" "};\n", 55, }, { "u64-read", "let val: u64 = 123u64;\n" "fn main() i32 = { return val: i32; };\n", 123, }, { "str-zeroinit", /* `let msg: str;` zero-inits the {ptr,len} slot. Assigning * a strlit at runtime updates both halves; .len then reads * the stored length. */ "let msg: str;\n" "fn main() i32 = {\n" "\tmsg = \"hello\";\n" "\treturn msg.len: i32;\n" "};\n", 5, }, { "str-reassign", "let msg: str;\n" "fn set(s: str) void = { msg = s; };\n" "fn main() i32 = {\n" "\tset(\"abcdefg\");\n" "\treturn msg.len: i32;\n" "};\n", 7, }, { "str-literal-init", /* `let s: str = "literal";` — the ptr half is patched at * link time by an R_X86_64_64 reloc against the strlit's * data label; .len comes baked into the DATAW payload. */ "let g: str = \"hello world\";\n" "fn main() i32 = { return g.len: i32; };\n", 11, }, { "str-literal-via-fn", /* Confirm the patched ptr really points at the right bytes * by reading the first byte of g.ptr through a pointer * cast. 'h' == 104. */ "let g: str = \"hello\";\n" "fn main() i32 = {\n" "\tlet p: *u8 = g.ptr;\n" "\treturn p[0]: i32;\n" "};\n", 104, }, { "slice-zeroinit", /* Slice globals start as {nil, 0, 0}. .len and .cap both * read zero; .ptr is nil but we don't dereference it. */ "let buf: []u8;\n" "fn main() i32 = {\n" "\treturn (buf.len + buf.cap): i32;\n" "};\n", 0, }, { "slice-cap-via-raw-pointer", /* Drive the slice header through a raw u64 pointer cast. * Pre-fix for slice reassignment this was the only way to * fill the header from ww source; kept because the path * still has to work. */ "let buf: []u8;\n" "fn main() i32 = {\n" "\tlet p: *u64 = (&buf): *u64;\n" "\tp[2] = 99u64;\n" "\treturn buf.cap: i32;\n" "};\n", 99, }, { "slice-global-reassign-from-global", /* `dst = src` for slice globals — both halves of the * triple (ptr, len, cap) must propagate. */ "let dst: []u8;\n" "let src: []u8;\n" "fn main() i32 = {\n" "\tlet p: *u64 = (&src): *u64;\n" "\tp[0] = 0x1000u64;\n" "\tp[1] = 7u64;\n" "\tp[2] = 99u64;\n" "\tdst = src;\n" "\treturn dst.len: i32 + dst.cap: i32;\n" "};\n", 106, }, { "slice-local-reassign-from-slice-expr", /* `s = arr[lo:hi]` reassigns the slice local; cgexpr now * emits N_SLICE as a triple. Previously the let-init * specific N_SLICE path worked but reassignment dropped * len/cap. */ "fn main() i32 = {\n" "\tlet arr: [16]u8;\n" "\tlet i: i32 = 0;\n" "\tfor (i < 16) { arr[i] = i: u8; i += 1; };\n" "\tlet s: []u8 = arr[3:10];\n" "\ts = arr[1:5];\n" "\treturn s.len: i32;\n" "};\n", 4, }, { "slice-local-from-fn-return", /* fn returning []u8 leaves (AX,BX,CX) at return; the let- * init slice fallback stores the triple. */ "fn mkslice() []u8 = {\n" "\tlet arr: [16]u8;\n" "\treturn arr[3:9];\n" "};\n" "fn main() i32 = {\n" "\tlet s: []u8 = mkslice();\n" "\treturn s.len: i32;\n" "};\n", 6, }, { "struct-field-readwrite", /* Top-level struct global. Field writes hit the global * via LEAQ name(SB) + offset; reads pull each field * with the right width. */ "type point = struct { x: i32, y: i32 };\n" "let p: point;\n" "fn main() i32 = {\n" "\tp.x = 7;\n" "\tp.y = 35;\n" "\treturn p.x + p.y;\n" "};\n", 42, }, { "struct-field-compound", /* Compound += on a struct global field — load via * LEAQ+disp, push, eval rhs, combine, store. */ "type counter = struct { n: i64 };\n" "let c: counter;\n" "fn bump(d: i64) void = { c.n += d; };\n" "fn main() i32 = {\n" "\tbump(10i64);\n" "\tbump(15i64);\n" "\tbump(17i64);\n" "\treturn c.n: i32;\n" "};\n", 42, }, { "f64-literal-init", /* `let pi: f64 = 3.14;` — DATAW bakes the 8 LE bytes of * the double directly. Read goes LEAQ name(SB),CX + * MOVSD (CX),X0; cast truncates to i32. */ "let pi: f64 = 7.5;\n" "fn main() i32 = { return pi: i32; };\n", 7, }, { "f32-literal-init", /* f32 globals are 4-byte slots; literal init bakes the * single-precision bit pattern. */ "let half: f32 = 4.25;\n" "fn main() i32 = { return half: i32; };\n", 4, }, { "f64-zero-init", "let z: f64;\n" "fn main() i32 = { return z: i32; };\n", 0, }, { "f64-reassign", /* Reassigning an f64 global goes LEAQ name(SB),CX + * MOVSD X0,(CX); read-back through the same shape. */ "let pi: f64 = 1.0;\n" "fn main() i32 = {\n" "\tpi = 7.5;\n" "\treturn pi: i32;\n" "};\n", 7, }, { "f64-arith", "let a: f64 = 7.5;\n" "let b: f64 = 4.25;\n" "fn main() i32 = {\n" "\tlet s: f64 = a + b;\n" "\treturn s: i32;\n" "};\n", 11, }, { "struct-tagged-field-i64", /* Tagged-union field on a struct global. Write goes * LEAQ name(SB),CX + MOVQ to slot+foff+8 (value) and * slot+foff+0 (tag). Read pulls AX=tag, DX=val0, * CX=val1 from those same slots and match dispatches. */ "type tok = (i64 | str | void);\n" "type ent = struct { id: i32, t: tok };\n" "let e: ent;\n" "fn main() i32 = {\n" "\te.t = 42i64: tok;\n" "\tlet v: tok = e.t;\n" "\tmatch (v) {\n" "\tcase let n: i64 => return n: i32;\n" "\tcase let s: str => return 1;\n" "\tcase void => return 2;\n" "\t};\n" "\treturn 0;\n" "};\n", 42, }, { "struct-tagged-field-str", "type tok = (i64 | str | void);\n" "type ent = struct { id: i32, t: tok };\n" "let e: ent;\n" "fn main() i32 = {\n" "\te.t = \"hello\";\n" "\tlet v: tok = e.t;\n" "\tmatch (v) {\n" "\tcase let n: i64 => return 1;\n" "\tcase let s: str => return s.len: i32;\n" "\tcase void => return 2;\n" "\t};\n" "\treturn 0;\n" "};\n", 5, }, { "struct-tagged-field-void", "type tok = (i64 | str | void);\n" "type ent = struct { id: i32, t: tok };\n" "let e: ent;\n" "fn main() i32 = {\n" "\te.t = void;\n" "\tlet v: tok = e.t;\n" "\tmatch (v) {\n" "\tcase let n: i64 => return 1;\n" "\tcase let s: str => return 2;\n" "\tcase void => return 7;\n" "\t};\n" "\treturn 0;\n" "};\n", 7, }, { "struct-narrow-field", /* u8 field on a struct global. Read uses MOVZBQ, store * uses MOVB. Pre-fix this would have stomped neighbouring * bytes by emitting MOVQ. */ "type packet = struct { hdr: u8, body: u32 };\n" "let pkt: packet;\n" "fn main() i32 = {\n" "\tpkt.hdr = 42u8;\n" "\tpkt.body = 999u32;\n" "\treturn pkt.hdr: i32;\n" "};\n", 42, }, { NULL, NULL, 0 } }; int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; int fail = 0, ran = 0; for (int i = 0; fixtures[i].label; i++, ran++) { int got = build_and_run(bin, fixtures[i].prog); if (got != fixtures[i].want) { fprintf(stderr, "let_global[%s]: exit=%d, want %d\n", fixtures[i].label, got, fixtures[i].want); fail++; } } if (fail) { fprintf(stderr, "let_global: %d/%d fixtures failed\n", fail, ran); return 1; } printf("let_global: %d/%d ok\n", ran, ran); return 0; }