Third step toward writable globals. The C cgen and its selfhost
mirror now:
- emit DATAW <name>(SB),"<8 LE bytes>" for every top-level `let`
whose type lands in the scalar set (i8..i64/u8..u64/bool/rune/
int/uint/uintptr/ptr; floats and multi-word types deferred);
- drop the "no writable .data" silent-drop guard at the N_IDENT
store path, replacing it with a RIP-relative MOVQ for `=` and
a load→combine→store sequence for the compound ops; and
- route `&name` through LEAQ name(SB) instead of dropping it.
Type aliases resolve via aliaslookup so `type counter = i32; let c:
counter = 0;` still emits a DATAW slot. Non-literal initialisers
silently skip, which surfaces as a clean undefined-symbol error if
the binding is ever referenced.
The selfhost mirror lands in the same commit because test 990
diffs the C cgen against wwdump_ww -c on err.ww (which has
top-level `let nerrors: i32 = 0; ... nerrors += 1;`). Any drift
between the two cgens makes 990 fail. Bootstrap stays at a fixed
point: ww2 == ww3 == ww4 byte-identical.
119 lines
2.6 KiB
C
119 lines
2.6 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,
|
|
},
|
|
{ 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;
|
|
}
|