N_IDENT for a slice local now loads (AX=ptr, BX=len, CX=cap), matching the existing global-slice load. cgexpr learns N_SLICE: `base[lo:hi]` leaves the same triple in registers, so callers (return, arg push, reassignment) all share one shape. The let-init's pre-existing N_SLICE direct-store path stays as a specialisation; the new generic slice let-init catches fn-returning-slice and slice-ident initialisers. N_ASSIGN gains a TY_SLICE branch parallel to TY_STR: store all three halves to the local slot or, for globals, stash CX into DI before LEAQ-ing the address (CX is both the new cap and the address scratch).
374 lines
8.8 KiB
C
374 lines
8.8 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/wait.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;
|
|
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;
|
|
}
|