Files
ww/test/wcc/630_let_global.c
Hojun-Cho 003f707618 w6c: emit DATAR for let s: str = "literal" initialisers
Use the new DATAR mechanism so str-literal init on a top-level
mutable `let` lands in .data and links cleanly.

emit_lets, when it sees `let s: str = "lit"` (non-empty strlit),
emits:

  DATAW s(SB),"<8 zero placeholder><8 LE bytes of len>"
  DATAR s+0(SB),<strlit_label>(SB)

The linker patches the placeholder with the strlit's runtime VA at
program load time, so `s.ptr` reads as the real pointer and `s.len`
as the literal length. A new let_pre_intern pass scans top-level
lets ahead of emit_data so the strlit gets a DATA row in the same
.s file; running emit_lets after emit_data instead would have
flipped the (DATA strlits, DATAW lets) section order in the .s and
broken byte-identity with the wwstage cgen.

The wwstage cgen still emits the zero-init shape for str lets,
which only matters if the wwstage is asked to compile source that
uses str-literal init. None of the selfhost combined sources do
that today, so test 994 / 990 stay green. The selfhost mirror for
DATAR + DATAW + this w6c branch is a follow-up.

630_let_global gains two fixtures: a length-readback and a first-
byte readback through the patched ptr.
2026-05-12 12:49:01 +09:00

230 lines
5.3 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
* — the language has no slice-reassignment expression
* yet, so this is the only way to fill the header from
* ww source today. .cap reads back as the value we
* wrote. */
"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,
},
{
"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,
},
{
"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;
}