ww: rename toolchain to w-prefix + hare-style build/run/test driver

Plan 9-style w-prefix on the per-arch tools, disambiguating from the
real Plan 9 6c/6a/6l in ref/plan9front/:

    cmd/wwc/      → cmd/wcc/        libwwc.a → libwcc.a
    cmd/6{c,a,l}  → cmd/w6{c,a,l}   binary names too
    test/wwc/     → test/wcc/       6 test files w/ w6 prefix
    selfhost/cmd  mirror in lockstep
    bootstrap/amd64/{w6c,w6a,w6l}   snapshot binaries (gitignored)
    WW_6{C,A,L}   → WW_W6{C,A,L}    env-var overrides

Plan 9 source-tree refs ("Plan 9 6c shape", ref/plan9front/, etc.)
preserved. Hare-style driver, both C and ww sides:

    ww test [path]   discover *_test.ww in a directory module, run
                     each; single-file mode for `ww test foo.ww`
    Module-by-name   `ww build foo` resolves to foo.ww or foo/foo.ww
                     via search path (cwd : -I dirs : $WW_LIB)
    Default-to-cwd   `ww build` / `ww test` build the cwd module
    Run pass-through `ww run path arg1 arg2` reaches the program

lib/os: getcwd (79) and getdents64 (217) syscalls power `.` resolution
and directory enumeration on the ww side.

Makefile: wwstage tool deps now include lib/os/os.ww (+ lib/strconv
for wwdump_ww) so lib/* edits force their rebuild instead of leaving
stale binaries — surfaced when test 995 first failed against a stale
w6c_ww built before the lib/os additions.

Test 993 byte-identical parity gate (C-side ww vs ww-side ww_ww on a
build corpus) stays green; all 19 tests pass.
This commit is contained in:
2026-05-11 13:49:27 +09:00
parent e217cd32d1
commit 2c33228b7e
96 changed files with 2129 additions and 973 deletions

87
test/wcc/000_smoke.c Normal file
View File

@@ -0,0 +1,87 @@
/*
* 000_smoke — Phase 0 smoke test.
*
* Asserts:
* - libwcc symbols (newarena/amalloc/freearena, fatal/errorf/warnf)
* are linkable.
* - `ww -V` exits 0 and prints the configured version.
*/
#include "ww.h"
#include <stdlib.h>
#include <string.h>
static void
test_arena(void)
{
Arena *a = newarena();
if (a == NULL) {
fprintf(stderr, "smoke: newarena returned NULL\n");
exit(1);
}
int *p = amalloc(a, sizeof *p);
*p = 42;
if (*p != 42) {
fprintf(stderr, "smoke: arena alloc bad\n");
exit(1);
}
/* force several growths */
for (int i = 0; i < 10000; i++) {
char *s = aprintf(a, "hello-%d", i);
if (s == NULL || strncmp(s, "hello-", 6) != 0) {
fprintf(stderr, "smoke: aprintf bad\n");
exit(1);
}
}
freearena(a);
}
static void
test_version(void)
{
if (WW_VERSION == NULL || WW_VERSION[0] == '\0') {
fprintf(stderr, "smoke: empty WW_VERSION\n");
exit(1);
}
}
static void
test_ww_minus_v(void)
{
const char *bin = getenv("BIN");
if (bin == NULL)
bin = "out/bin";
char cmd[512];
snprintf(cmd, sizeof cmd, "%s/ww -V", bin);
FILE *p = popen(cmd, "r");
if (p == NULL) {
fprintf(stderr, "smoke: popen %s\n", cmd);
exit(1);
}
char buf[128];
size_t n = fread(buf, 1, sizeof buf - 1, p);
buf[n] = '\0';
int rc = pclose(p);
if (rc != 0) {
fprintf(stderr, "smoke: ww -V exit %d\n", rc);
exit(1);
}
if (strstr(buf, "ww ") != buf) {
fprintf(stderr, "smoke: ww -V output not 'ww ...': %s\n", buf);
exit(1);
}
if (strstr(buf, WW_VERSION) == NULL) {
fprintf(stderr, "smoke: ww -V missing version %s in: %s\n",
WW_VERSION, buf);
exit(1);
}
}
int
main(void)
{
test_arena();
test_version();
test_ww_minus_v();
puts("smoke: ok");
return 0;
}

142
test/wcc/100_lex.c Normal file
View File

@@ -0,0 +1,142 @@
/*
* 100_lex — table-driven lexer tests.
*
* Each row is a (src, expected) pair. The expected string is the
* concatenation of token names, space-separated. For literals we
* also encode the value: e.g. INT(42), STR("hi"), IDENT(foo).
*
* EOF is implicit: the harness checks that lexnext returns TK_EOF
* after the last expected token.
*/
#include "ww.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static char *
toklit(Arena *a, Tok t)
{
switch (t.kind) {
case TK_IDENT: return aprintf(a, "IDENT(%s)", t.text);
case TK_INT: return aprintf(a, "INT(%llu)", (unsigned long long)t.v.uval);
case TK_FLOAT: return aprintf(a, "FLOAT(%g)", t.v.fval);
case TK_RUNE: return aprintf(a, "RUNE(%llu)", (unsigned long long)t.v.uval);
case TK_STR: return aprintf(a, "STR(%s)", t.text);
case TK_ERR: return aprintf(a, "ERR(%s)", t.text);
default: return (char *)tokname(t.kind);
}
}
static int
runrow(const char *src, const char *expect)
{
Arena *a = newarena();
Lex l;
lexinit(&l, a, "<test>", src, strlen(src));
char *got = amalloc(a, 1);
got[0] = '\0';
u64 cap = 1, n = 0;
for (;;) {
Tok t = lexnext(&l);
if (t.kind == TK_EOF)
break;
const char *piece = toklit(a, t);
u64 plen = strlen(piece);
u64 need = n + plen + 2;
if (need >= cap) {
u64 nc = need * 2;
char *nb = amalloc(a, nc);
memcpy(nb, got, n);
got = nb;
cap = nc;
}
if (n) got[n++] = ' ';
memcpy(got + n, piece, plen);
n += plen;
got[n] = '\0';
}
int ok = strcmp(got, expect) == 0;
if (!ok) {
fprintf(stderr, "lex mismatch:\n src: %s\n"
" want: %s\n got: %s\n", src, expect, got);
}
freearena(a);
return ok;
}
struct row { const char *src, *expect; };
static const struct row rows[] = {
{ "", "" },
{ " \t\n ", "" },
{ "// comment\n", "" },
{ "/* a /b/ c */", "" },
/* identifiers + keywords */
{ "foo", "IDENT(foo)" },
{ "fn", "fn" },
{ "fn main", "fn IDENT(main)" },
{ "let x: i32 = 0;", "let IDENT(x) : IDENT(i32) = INT(0) ;" },
{ "export fn", "export fn" },
{ "if else for switch case return use type struct defer break continue proc chan nil true false",
"if else for switch case return use type struct defer break continue proc chan nil true false" },
/* numbers */
{ "0", "INT(0)" },
{ "42", "INT(42)" },
{ "1_000_000", "INT(1000000)" },
{ "0xff", "INT(255)" },
{ "0xDE_AD_BE_EF", "INT(3735928559)" },
{ "0b1010", "INT(10)" },
{ "0o777", "INT(511)" },
{ "3.14", "FLOAT(3.14)" },
{ "1.5e3", "FLOAT(1500)" },
/* strings & runes */
{ "\"hello\"", "STR(hello)" },
{ "\"a\\nb\"", "STR(a\nb)" },
{ "'A'", "RUNE(65)" },
{ "'\\n'", "RUNE(10)" },
{ "'\\x7f'", "RUNE(127)" },
/* operators & punct */
{ "+ - * / % == != < > <= >= && || !",
"+ - * / % == != < > <= >= && || !" },
{ "= += -= *= /= %= &= |= ^= <<= >>=",
"= += -= *= /= %= &= |= ^= <<= >>=" },
{ "<< >> & | ^ ~ ?",
"<< >> & | ^ ~ ?" },
{ "( ) { } [ ] , ; : . ... @",
"( ) { } [ ] , ; : . ... @" },
{ "<- ->",
"<- ->" },
{ "@symbol(\"malloc\")",
"@ IDENT(symbol) ( STR(malloc) )" },
/* mixed */
{ "fn add(a: i32, b: i32) i32 = { return a + b; };",
"fn IDENT(add) ( IDENT(a) : IDENT(i32) , IDENT(b) : IDENT(i32) ) IDENT(i32) = { return IDENT(a) + IDENT(b) ; } ;" },
};
int
main(void)
{
int fail = 0;
for (size_t i = 0; i < sizeof rows / sizeof rows[0]; i++) {
if (!runrow(rows[i].src, rows[i].expect)) {
fprintf(stderr, "row %zu failed\n", i);
fail++;
}
}
if (fail) {
fprintf(stderr, "%d/%zu lex tests failed\n", fail,
sizeof rows / sizeof rows[0]);
return 1;
}
printf("lex: %zu/%zu ok\n", sizeof rows / sizeof rows[0],
sizeof rows / sizeof rows[0]);
return 0;
}

190
test/wcc/200_parse.c Normal file
View File

@@ -0,0 +1,190 @@
/*
* 200_parse — parser tests.
*
* Two flavours:
* 1) "must parse": source must produce a Node and no errors.
* 2) "shape match": parsed AST printed via astprint, compared against
* expected substring (so tests stay readable without binding to
* every position field).
*/
#include "ww.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static int
must_parse(const char *src)
{
Arena *a = newarena();
Lex l;
Parser p;
lexinit(&l, a, "<test>", src, strlen(src));
parserinit(&p, a, &l);
Node *n = parsefile(&p);
int ok = (n != NULL && p.errs == 0 && l.errs == 0);
freearena(a);
return ok;
}
static char *
parse_to_str(const char *src, int *errs)
{
Arena *a = newarena();
Lex l;
Parser p;
lexinit(&l, a, "<test>", src, strlen(src));
parserinit(&p, a, &l);
Node *n = parsefile(&p);
*errs = p.errs + l.errs;
char *buf = NULL;
size_t len = 0;
FILE *f = open_memstream(&buf, &len);
if (f == NULL) {
freearena(a);
return NULL;
}
astprint(f, n);
fclose(f);
char *out = malloc(len + 1);
memcpy(out, buf, len);
out[len] = '\0';
free(buf);
freearena(a);
return out;
}
static int
must_contain(const char *src, const char *needle)
{
int errs;
char *got = parse_to_str(src, &errs);
if (got == NULL || errs > 0) {
fprintf(stderr, "parse errs=%d:\n%s\n", errs, src);
free(got);
return 0;
}
if (strstr(got, needle) == NULL) {
fprintf(stderr, "shape mismatch:\n src: %s\n"
" want: %s\n got:\n%s\n", src, needle, got);
free(got);
return 0;
}
free(got);
return 1;
}
int
main(void)
{
int fail = 0;
const char *parses[] = {
"use io;",
"use io.bufio;",
"def MAX: i32 = 4096;",
"export def MAX: i32 = 4096;",
"type point = struct { x: i32, y: i32 };",
"type stream = fn(b: []u8) i32;",
"type chans = chan i32;",
"type box = struct { p: *point, n: i32, items: []i32 };",
"type arr = [16]u8;",
"fn nop() void = {};",
"export fn id(x: i32) i32 = { return x; };",
"fn add(a: i32, b: i32) i32 = { return a + b; };",
"@symbol(\"malloc\") fn cmalloc(n: u64) *void;",
"fn varadic(a: i32, ...) void;",
"fn f() void = { let x: i32 = 0; let y = 1.5; let s: str = \"hi\"; };",
"fn f() void = { if (x > 0) { return; } else { x += 1; }; };",
"fn f() void = { for (let i: i32 = 0; i < 10; i += 1) { x += i; }; };",
"fn f() void = { for (i < 10) { i += 1; }; };",
"fn f() void = { for () { i += 1; }; };",
"fn f() void = { defer free(p); };",
"fn f() void = { switch (x) { case 1, 2: y = 1; case: y = 0; }; };",
"fn ptr(p: *point) i32 = { return p.x; };",
"fn cast() void = { let x = (5 + 1): i64; };",
"fn deref(p: *i32) i32 = { return *p; };",
"fn addr(x: i32) *i32 = { return &x; };",
"fn lit() void = { let p: point = point { x = 1, y = 2 }; };",
"fn pkg() void = { fmt.println(1); };",
"fn arr() void = { let a = [1, 2, 3]; };",
"fn idx() i32 = { return a[3]; };",
"fn neg() i32 = { return -a + ~b * !c; };",
"fn ops() void = { x = 1; x += 1; x -= 1; x *= 2; x /= 2; x %= 2; "
"x &= 1; x |= 1; x ^= 1; x <<= 1; x >>= 1; };",
"fn cmp() bool = { return a == b && c != d || e < f && g <= h; };",
"fn bits() i32 = { return (a & b) | (c ^ d); };",
"fn shift() i32 = { return a << 2 | b >> 1; };",
"fn nested() void = { if (a > 0) { if (b > 0) { c = 1; }; }; };",
"fn many(a: i32, b: i32, c: i32, d: i32, e: i32) i32 = "
"{ return a + b * c - d / e; };",
"fn slc(s: []u8) []u8 = { return s; };",
"fn ssn(p: **point) i32 = { return (*p).x; };",
"fn arrptr(p: *[16]u8) u8 = { return p[0]; };",
"fn fnt(f: fn(i32) i32, x: i32) i32 = { return f(x); };",
"fn anontype() void = { let f: fn(i32) i32 = id; };",
/* multiple decls */
"use io;\nuse fmt;\ndef N: i32 = 8;\ntype p = struct{x:i32};\nfn f() void = {};",
/* attribute on FFI decl */
"@symbol(\"strlen\") fn cstrlen(s: *u8) u64;",
/* trailing comma */
"fn f(a: i32, b: i32,) void = {};",
/* nil/true/false */
"fn f() void = { let p: *i32 = nil; let x: bool = true; let y: bool = false; };",
/* chan */
"fn ch() void = { let c: chan i32; let v = <-c; };",
/* nested struct lit */
"fn lit2() void = { let q = box { p = nil, n = 0, items = [1, 2] }; };",
/* defer with call */
"fn f() void = { defer close(fd); return; };",
/* break / continue */
"fn f() void = { for () { if (x) { break; }; continue; }; };",
/* deeply nested expr */
"fn deep() i32 = { return ((((((1 + 2) * 3) - 4) / 5) % 6) << 7); };",
/* index chain */
"fn ix() i32 = { return a[b][c[d]]; };",
/* dot chain */
"fn dot() i32 = { return a.b.c.d; };",
/* call chain */
"fn cc() i32 = { return f()()(); };",
/* mixed postfix */
"fn mx() i32 = { return obj.method(arg)[idx].field; };",
/* multi-return tuples */
"fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };",
"fn try() (i32, str) = { return 0, \"\"; };",
"fn use_tuple() void = { let q, r = divmod(10, 3); };",
"fn assign_tuple() void = { q, r = divmod(10, 3); };",
};
int n = sizeof parses / sizeof parses[0];
for (int i = 0; i < n; i++) {
if (!must_parse(parses[i])) {
fprintf(stderr, "parse fail [%d]: %s\n", i, parses[i]);
fail++;
}
}
if (!must_contain("use io;", "(use \"io\"")) fail++;
if (!must_contain("def N: i32 = 4;", "(def \"N\"")) fail++;
if (!must_contain("def N: i32 = 4;", "(int 4")) fail++;
if (!must_contain("export fn f() void = {};", "(fn \"f\" export")) fail++;
if (!must_contain("type p = struct { x: i32 };", "(typedecl \"p\"")) fail++;
if (!must_contain("fn f() i32 = { return 1; };", "(return")) fail++;
if (!must_contain("fn f() void = { x = 1; };", "(assign =")) fail++;
if (!must_contain("fn f() i32 = { return a + b; };", "(bin +")) fail++;
if (!must_contain("@symbol(\"x\") fn f() void;", "(attr \"symbol\""))fail++;
if (fail) {
fprintf(stderr, "%d parse tests failed\n", fail);
return 1;
}
printf("parse: %d/%d ok + 9 shape ok\n", n, n);
return 0;
}

126
test/wcc/300_check.c Normal file
View File

@@ -0,0 +1,126 @@
/*
* 300_check — type-checker tests.
*
* Each row is (src, expect) where expect is "ok" (no errors) or a
* substring that must appear in the captured stderr.
*/
#include "ww.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static int
runrow(const char *src, const char *expect)
{
Arena *a = newarena();
Lex l;
Parser p;
Checker c;
lexinit(&l, a, "<test>", src, strlen(src));
parserinit(&p, a, &l);
Node *file = parsefile(&p);
char *errbuf = NULL;
size_t errlen = 0;
FILE *prev = errout;
errout = open_memstream(&errbuf, &errlen);
check_init(&c, a);
check_file(&c, file);
fclose(errout);
errout = prev;
int ok = 0;
if (strcmp(expect, "ok") == 0) {
ok = (c.errs == 0 && p.errs == 0 && l.errs == 0);
if (!ok)
fprintf(stderr, "expected ok but got %d errors:\n%s"
" src: %s\n", c.errs + p.errs + l.errs, errbuf, src);
} else {
ok = (errbuf && strstr(errbuf, expect) != NULL);
if (!ok)
fprintf(stderr, "expected substring '%s' in errs:\n%s"
" src: %s\n", expect, errbuf, src);
}
free(errbuf);
freearena(a);
return ok;
}
struct row { const char *src, *expect; };
static const struct row rows[] = {
/* OK cases */
{ "fn main() void = {};", "ok" },
{ "fn id(x: i32) i32 = { return x; };", "ok" },
{ "fn add(a: i32, b: i32) i32 = { return a + b; };", "ok" },
{ "def MAX: i32 = 4096;", "ok" },
{ "type point = struct { x: i32, y: i32 };", "ok" },
{ "type point = struct { x: i32, y: i32 };\n"
"fn move(p: *point, dx: i32) void = { p.x += dx; };", "ok" },
{ "fn nums() void = { let x: i32 = 1; let y: i64 = 2; };", "ok" },
{ "fn cond(x: i32) i32 = { if (x > 0) { return 1; }; return 0; };", "ok" },
{ "fn lo() void = { for (let i: i32 = 0; i < 10; i += 1) { }; };", "ok" },
{ "fn ptr(p: *i32) i32 = { return *p; };", "ok" },
{ "fn addr(x: i32) *i32 = { return &x; };", "ok" },
{ "fn cmp(a: i32, b: i32) bool = { return a == b; };", "ok" },
{ "fn b() bool = { return true && false || !true; };", "ok" },
{ "fn cast() void = { let x = (5 + 1): i64; };", "ok" },
{ "fn slc(s: []u8) []u8 = { return s; };", "ok" },
{ "fn arr() void = { let a: [16]u8; };", "ok" },
{ "fn lit() void = { let p = point { x = 1, y = 2 }; };\n"
"type point = struct { x: i32, y: i32 };", "ok" },
{ "fn idx(a: []i32) i32 = { return a[0]; };", "ok" },
{ "fn nilptr(p: *i32) bool = { return p == nil; };", "ok" },
{ "fn untyped() void = { let x: i64 = 42; };", "ok" },
{ "fn fld(p: *point) i32 = { return p.x; };\n"
"type point = struct { x: i32 };", "ok" },
{ "fn loops() void = { for () { break; }; for () { continue; }; };", "ok" },
/* multi-return tuples */
{ "fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };", "ok" },
{ "fn dm(a: i64, b: i64) (i64, i64) = { return a, b; };\n"
"fn caller() i64 = { let q, r = dm(10, 3); return q + r; };", "ok" },
{ "fn dm() (i64, i64) = { return 1, 2; };\n"
"fn ass() void = { let q: i64 = 0; let r: i64 = 0; q, r = dm(); };", "ok" },
/* error cases */
{ "fn f() void = { return 1; };", "return value in void" },
{ "fn f() i32 = { return; };", "not assignable" },
{ "fn f() void = { x = 1; };", "undefined" },
{ "fn f() void = { let x: nope = 1; };", "unknown type" },
{ "fn f() void = { let x: bool = 1; };", "not assignable" },
{ "fn f() i32 = { return \"hi\"; };", "not assignable" },
{ "fn f() void = { 1 + true; };", "non-numeric" },
{ "fn f() void = { -true; };", "non-numeric" },
{ "fn f() void = { *5; };", "deref non-pointer" },
{ "fn f() void = { let x: i32 = 1; let x: i32 = 2; };",
"ok" }, /* shadowing in inner scope; same scope flagged */
{ "fn f() void = { break; };", "break outside loop" },
{ "fn f() void = { continue; };", "continue outside loop" },
{ "fn f(x: i32) void = { x[0]; };", "indexing non-indexable" },
{ "fn f() i32 = { return 1; }; fn g() i32 = { return f(1); };",
"too many arguments" },
{ "fn f(a: i32) i32 = { return a; }; fn g() i32 = { return f(); };",
"not enough arguments" },
{ "fn f() void = { if (1) { }; };", "if condition" },
};
int
main(void)
{
int n = sizeof rows / sizeof rows[0];
int fail = 0;
for (int i = 0; i < n; i++)
if (!runrow(rows[i].src, rows[i].expect)) {
fprintf(stderr, "row %d failed\n", i);
fail++;
}
if (fail) {
fprintf(stderr, "%d/%d check tests failed\n", fail, n);
return 1;
}
printf("check: %d/%d ok\n", n, n);
return 0;
}

90
test/wcc/400_w6c.c Normal file
View File

@@ -0,0 +1,90 @@
/*
* 400_6c — codegen smoke tests. Each row supplies a tiny ww source
* and a list of substrings expected in the emitted .s. We don't
* golden-match the whole file (too brittle); we just verify that
* key opcodes and operand shapes show up.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int
run6c(const char *src, char **out)
{
char path[64];
snprintf(path, sizeof path, "/tmp/wwt_%d.ww", getpid());
FILE *f = fopen(path, "wb");
if (f == NULL) return -1;
fputs(src, f);
fclose(f);
char cmd[256];
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
snprintf(cmd, sizeof cmd, "%s/w6c %s 2>&1", bin, path);
FILE *p = popen(cmd, "r");
if (p == NULL) { unlink(path); return -1; }
size_t cap = 4096, n = 0;
char *buf = malloc(cap);
int c;
while ((c = fgetc(p)) != EOF) {
if (n + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); }
buf[n++] = (char)c;
}
buf[n] = '\0';
int rc = pclose(p);
unlink(path);
*out = buf;
return rc;
}
static int
contains(const char *hay, const char *needle)
{
return strstr(hay, needle) != NULL;
}
struct row { const char *src; const char *needle; };
static const struct row rows[] = {
{ "fn main() i32 = { return 42; };", "MOVQ\t$42, AX" },
{ "fn main() i32 = { return 42; };", "RET" },
{ "fn id(x: i32) i32 = { return x; };","MOVQ\tDI, -8(BP)" },
{ "fn add(a: i32, b: i32) i32 = { return a + b; };", "ADDQ\tBX, AX" },
{ "fn sub(a: i32, b: i32) i32 = { return a - b; };", "SUBQ\tBX, AX" },
{ "fn mul(a: i32, b: i32) i32 = { return a * b; };", "IMULQ\tBX, AX" },
{ "fn neg(x: i32) i32 = { return -x; };", "NEGQ\tAX" },
{ "fn cmp(a: i32, b: i32) bool = { return a == b; };", "CMPQ\tBX, AX" },
{ "fn cmp(a: i32, b: i32) bool = { return a == b; };", "JE" },
{ "fn cond(x: i32) i32 = { if (x > 0) { return 1; }; return 0; };",
"CMPQ\t$0, AX" },
{ "fn lo() void = { for (let i: i32 = 0; i < 10; i += 1) { }; };", "JMP" },
{ "fn callit() i32 = { return 1; };", "TEXT callit,$0" },
};
int
main(void)
{
int n = sizeof rows / sizeof rows[0];
int fail = 0;
for (int i = 0; i < n; i++) {
char *out = NULL;
int rc = run6c(rows[i].src, &out);
if (rc != 0) {
fprintf(stderr, "row %d: w6c rc=%d\n", i, rc);
fail++;
free(out);
continue;
}
if (!contains(out, rows[i].needle)) {
fprintf(stderr, "row %d: missing '%s' in:\n%s\n",
i, rows[i].needle, out);
fail++;
}
free(out);
}
if (fail) { fprintf(stderr, "%d/%d w6c tests failed\n", fail, n); return 1; }
printf("w6c: %d/%d ok\n", n, n);
return 0;
}

62
test/wcc/500_w6a.c Normal file
View File

@@ -0,0 +1,62 @@
/*
* 500_asm — assembler smoke. Drive w6c on a tiny program, feed the
* output to w6a, then read back the .o magic to confirm we produced
* a valid ELF64 relocatable object.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int
run(const char *cmd)
{
return system(cmd);
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char src[64], asmf[64], obj[64];
snprintf(src, sizeof src, "/tmp/wwt_%d.ww", getpid());
snprintf(asmf, sizeof asmf, "/tmp/wwt_%d.s", getpid());
snprintf(obj, sizeof obj, "/tmp/wwt_%d.o", getpid());
const char *programs[] = {
"fn main() i32 = { return 42; };",
"fn add(a: i32, b: i32) i32 = { return a + b; };",
"fn loop() i32 = { let i: i32 = 0; for (i < 10) { i += 1; }; return i; };",
"fn cmp(a: i32, b: i32) bool = { return a < b; };",
NULL
};
int n = 0, fail = 0;
for (int i = 0; programs[i]; i++, n++) {
FILE *f = fopen(src, "wb");
fputs(programs[i], f);
fclose(f);
char cmd[512];
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, asmf, src);
if (run(cmd) != 0) { fprintf(stderr, "w6c fail: %s\n", programs[i]); fail++; continue; }
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, asmf);
if (run(cmd) != 0) { fprintf(stderr, "w6a fail: %s\n", programs[i]); fail++; continue; }
FILE *of = fopen(obj, "rb");
if (of == NULL) { fail++; continue; }
unsigned char hdr[16];
size_t r = fread(hdr, 1, sizeof hdr, of);
fclose(of);
if (r != 16 || memcmp(hdr, "\x7f""ELF", 4) != 0
|| hdr[4] != 2 /* ELFCLASS64 */) {
fprintf(stderr, "not an ELF64: %s\n", programs[i]);
fail++;
}
}
unlink(src); unlink(asmf); unlink(obj);
if (fail) { fprintf(stderr, "%d/%d w6a tests failed\n", fail, n); return 1; }
printf("w6a: %d/%d ok\n", n, n);
return 0;
}

67
test/wcc/600_w6l.c Normal file
View File

@@ -0,0 +1,67 @@
/*
* 600_6l — linker smoke. Drive w6c → w6a → w6l on a tiny program,
* confirm the result is a static ELF executable with no PT_INTERP.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char src[64], asmf[64], obj[64], exe[64];
snprintf(src, sizeof src, "/tmp/wwt_%d.ww", getpid());
snprintf(asmf, sizeof asmf, "/tmp/wwt_%d.s", getpid());
snprintf(obj, sizeof obj, "/tmp/wwt_%d.o", getpid());
snprintf(exe, sizeof exe, "/tmp/wwt_%d.x", getpid());
FILE *f = fopen(src, "wb");
fputs("fn main() i32 = { return 42; };", f);
fclose(f);
char cmd[1024];
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, asmf, src);
if (system(cmd) != 0) { fprintf(stderr, "w6c failed\n"); return 1; }
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, asmf);
if (system(cmd) != 0) { fprintf(stderr, "w6a failed\n"); return 1; }
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s", bin, exe, obj);
if (system(cmd) != 0) { fprintf(stderr, "w6l failed\n"); return 1; }
/* validate ELF magic + e_type=EXEC */
FILE *of = fopen(exe, "rb");
if (of == NULL) { fprintf(stderr, "exe missing\n"); return 1; }
unsigned char hdr[20];
if (fread(hdr, 1, sizeof hdr, of) != sizeof hdr) { fprintf(stderr, "short exe\n"); fclose(of); return 1; }
fclose(of);
if (memcmp(hdr, "\x7f""ELF", 4) != 0 || hdr[4] != 2) {
fprintf(stderr, "not an ELF64\n"); return 1;
}
/* e_type at offset 16, little-endian u16; ET_EXEC = 2 */
unsigned short etype = (unsigned short)hdr[16] | ((unsigned short)hdr[17] << 8);
if (etype != 2) {
fprintf(stderr, "not ET_EXEC, got %u\n", etype); return 1;
}
/* ldd "not a dynamic executable" — proxy: check that file is not
* dynamic by looking for PT_INTERP. We have none, so ldd reports
* "not a dynamic executable" or similar. */
snprintf(cmd, sizeof cmd, "ldd %s 2>&1", exe);
FILE *p = popen(cmd, "r");
char buf[256] = {0};
fread(buf, 1, sizeof buf - 1, p);
pclose(p);
if (strstr(buf, "not a dynamic executable") == NULL
&& strstr(buf, "statically linked") == NULL) {
fprintf(stderr, "ldd says not static: %s\n", buf);
return 1;
}
unlink(src); unlink(asmf); unlink(obj); unlink(exe);
printf("w6l: ok\n");
return 0;
}

96
test/wcc/610_arch.c Normal file
View File

@@ -0,0 +1,96 @@
/*
* 610_arch — archive selectivity. Build two .o files into an archive
* where one defines a symbol main needs, and the other references an
* undefined external. Linking should pull only the needed member;
* the other one's bad reference must NOT cause a link error.
*
* If w6l were still pulling all members, this test would fail with
* "undefined reference to 'this_symbol_does_not_exist'".
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int run(const char *cmd) { return system(cmd); }
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char dir[64];
snprintf(dir, sizeof dir, "/tmp/wwarch_%d", getpid());
mkdir(dir, 0755);
/* lib_good.ww — defines f1() */
char path[256];
snprintf(path, sizeof path, "%s/lib_good.ww", dir);
FILE *f = fopen(path, "wb");
fputs("export fn f1() i32 = { return 7; };", f);
fclose(f);
/* lib_bad.ww — defines f2() but also references an undefined extern */
snprintf(path, sizeof path, "%s/lib_bad.ww", dir);
f = fopen(path, "wb");
fputs("@symbol(\"this_symbol_does_not_exist\") fn bogus() i32;\n"
"export fn f2() i32 = { return bogus(); };", f);
fclose(f);
/* main.ww — calls f1, NOT f2 */
snprintf(path, sizeof path, "%s/m.ww", dir);
f = fopen(path, "wb");
fputs("@symbol(\"f1\") fn f1() i32;\n"
"fn main() i32 = { return f1(); };", f);
fclose(f);
char cmd[2048];
snprintf(cmd, sizeof cmd,
"set -e; cd %s && %s/w6c -o lib_good.s lib_good.ww && "
"%s/w6c -o lib_bad.s lib_bad.ww && "
"%s/w6a -o lib_good.o lib_good.s && %s/w6a -o lib_bad.o lib_bad.s && "
"ar rcs libfoo.a lib_good.o lib_bad.o && "
"%s/w6c -o m.s m.ww && %s/w6a -o m.o m.s",
dir, bin, bin, bin, bin, bin, bin);
if (run(cmd) != 0) { fprintf(stderr, "build failed\n"); return 1; }
/* Find start.o for the runtime */
char startobj[256];
snprintf(startobj, sizeof startobj, "%s/../obj/rt/start.o", bin);
snprintf(cmd, sizeof cmd,
"%s/w6l -o %s/m %s/m.o %s %s/libfoo.a 2>%s/link.err",
bin, dir, dir, startobj, dir, dir);
if (run(cmd) != 0) {
FILE *ef = fopen("/dev/null", "r");
(void)ef;
char errpath[300];
snprintf(errpath, sizeof errpath, "%s/link.err", dir);
FILE *e = fopen(errpath, "r");
if (e) {
char buf[512]; size_t r = fread(buf, 1, sizeof buf - 1, e); buf[r]='\0'; fclose(e);
fprintf(stderr, "link failed:\n%s\n", buf);
}
return 1;
}
char exepath[256];
snprintf(exepath, sizeof exepath, "%s/m", dir);
int rc = run(exepath);
if (WIFEXITED(rc) && WEXITSTATUS(rc) == 7) {
printf("arch: ok (only needed archive member pulled)\n");
return 0;
}
fprintf(stderr, "arch: unexpected exit\n");
return 1;
}

883
test/wcc/700_e2e.c Normal file
View File

@@ -0,0 +1,883 @@
/*
* 700_e2e — end-to-end. Drive `ww build` on a small source program,
* run the produced binary, check the exit status. This is the real
* user-facing happy path.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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 *src; int want_exit; };
static const struct row rows[] = {
{ "fn main() i32 = { return 42; };", 42 },
{ "fn add(a: i32, b: i32) i32 = { return a + b; };\n"
"fn main() i32 = { return add(7, 35); };", 42 },
{ "fn main() i32 = {\n"
" let i: i32 = 0;\n"
" let s: i32 = 0;\n"
" for (i < 10) { s += i; i += 1; };\n"
" return s;\n"
"};", 45 },
{ "fn main() i32 = {\n"
" let x: i32 = 100;\n"
" if (x > 50) { return 1; };\n"
" return 0;\n"
"};", 1 },
{ "fn main() i32 = {\n"
" let a: i32 = 6;\n"
" let b: i32 = 7;\n"
" return a * b;\n"
"};", 42 },
/* multi-return tuple, divmod */
{ "fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };\n"
"fn main() i32 = {\n"
" let q, r = divmod(17, 5);\n"
" return (q + r): i32;\n"
"};", 5 },
/* fixed array, byte-wise read/write */
{ "fn main() i32 = {\n"
" let buf: [4]u8;\n"
" buf[0] = 1: u8;\n"
" buf[1] = 2: u8;\n"
" buf[2] = 3: u8;\n"
" buf[3] = 4: u8;\n"
" let sum: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < 4) { sum += buf[i]: i32; i += 1; };\n"
" return sum;\n"
"};", 10 },
/* float: arg, arith, literal, cast back to int */
{ "fn area(r: f64) f64 = { return 3.14 * r * r; };\n"
"fn main() i32 = { let a: f64 = area(5.0); return a: i32; };", 78 },
/* float comparison: must emit UCOMISD + JA (not CMPQ + JG) */
{ "fn main() i32 = {\n"
" let a: f64 = 1.5;\n"
" let b: f64 = 2.5;\n"
" if (a < b) { if (b > a) { return 7; }; };\n"
" return 0;\n"
"};", 7 },
/* float ==/!= via UCOMISD */
{ "fn main() i32 = {\n"
" let a: f64 = 3.14;\n"
" let b: f64 = 3.14;\n"
" if (a == b) { return 11; };\n"
" return 0;\n"
"};", 11 },
/* function pointer: take address of a named fn, call indirectly */
{ "fn add(a: i32, b: i32) i32 = { return a + b; };\n"
"fn main() i32 = {\n"
" let fp: fn(a: i32, b: i32) i32 = add;\n"
" return fp(20, 22);\n"
"};", 42 },
/* string literal via syscall — exit code = bytes written */
{ "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n"
"fn print(s: str) i64 = { return rt_syscall(1, 1, s.ptr: i64, s.len: i64); };\n"
"fn main() i32 = { return print(\"hello, world\\n\"): i32; };", 13 },
/* 9 args — 3 spill to the stack */
{ "fn s9(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32, i: i32) i32 = {\n"
" return a + b + c + d + e + f + g + h + i;\n"
"};\n"
"fn main() i32 = { return s9(1,2,3,4,5,6,7,8,9); };", 45 },
/* defer: LIFO at function return */
{ "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n"
"fn out(c: i32) void = { rt_syscall(1, 1, (&c): i64, 1); };\n"
"fn main() i32 = {\n"
" let c1: i32 = 0;\n"
" let c2: i32 = 0;\n"
" c1 = 65;\n" /* 'A' */
" c2 = 66;\n" /* 'B' */
" defer out(c1);\n"
" defer out(c2);\n"
" return 0;\n"
"};", 0 },
/* struct-by-value: 16B all-int passed by value */
{ "type pair = struct { a: i64, b: i64 };\n"
"fn sum(p: pair) i64 = { return p.a + p.b; };\n"
"fn main() i32 = {\n"
" let p: pair = pair { a = 10, b = 32 };\n"
" return sum(p): i32;\n"
"};", 42 },
/* f32 cast + arithmetic */
{ "fn add32(a: f32, b: f32) f32 = { return a + b; };\n"
"fn main() i32 = {\n"
" let r: f32 = add32(2.5: f32, 7.5: f32);\n"
" return r: i32;\n"
"};", 10 },
/* slice from array: build header, iterate via index/len */
{ "fn main() i32 = {\n"
" let arr: [4]u8;\n"
" arr[0] = 10: u8; arr[1] = 20: u8;\n"
" arr[2] = 30: u8; arr[3] = 99: u8;\n"
" let s: []u8 = arr[0:3];\n"
" let sum: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < s.len) { sum += s[i]: i32; i += 1; };\n"
" return sum;\n"
"};", 60 },
/* module imports: use os and call os.write; exit code = bytes */
{ "use os;\n"
"fn main() i32 = { return os.write(1, \"ok\\n\".ptr, 3): i32; };", 3 },
/* typed integer literals */
{ "fn main() i32 = {\n"
" let buf: [4]u8;\n"
" buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 0u8;\n"
" let s: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < 3) { s += buf[i]: i32; i += 1; };\n"
" return s;\n"
"};", 198 },
/* full stdlib stack: use os + strconv, slice-arg call, write
* the formatted number to stdout. exit code = number length. */
{ "use os;\n"
"use strconv;\n"
"fn main() i32 = {\n"
" let buf: [32]u8;\n"
" let s: []u8 = buf[0:32];\n"
" let n: i32 = strconv.i64toa(s, 12345);\n"
" os.write(1, buf.ptr, n: u64);\n"
" os.write(1, \"\\n\".ptr, 1u64);\n"
" return n;\n"
"};", 5 },
/* alloc + free via mmap-backed runtime — write through allocated
* memory and free it. exit = 0 if the allocation succeeded. */
{ "use os;\n"
"fn main() i32 = {\n"
" let p: *void = os.alloc(4096u64);\n"
" if (p == nil) { return 1; };\n"
" let bp: *u8 = p: *u8;\n"
" bp[0] = 65u8;\n"
" os.write(1, bp, 1u64);\n"
" os.free(p, 4096u64);\n"
" return 0;\n"
"};", 0 },
/* argv: kernel passes argc in DI, argv in SI. */
{ "fn main(argc: i32, argv: **u8) i32 = { return argc; };", 1 },
/* i64 array: scaled indexing (elem size 8) */
{ "fn main() i32 = {\n"
" let arr: [4]i64;\n"
" arr[0] = 10; arr[1] = 20; arr[2] = 30; arr[3] = 40;\n"
" let sum: i64 = 0;\n"
" let i: i32 = 0;\n"
" for (i < 4) { sum += arr[i]; i += 1; };\n"
" return sum: i32;\n"
"};", 100 },
/* break out of an infinite loop early */
{ "fn main() i32 = {\n"
" let i: i32 = 0;\n"
" for () {\n"
" i += 1;\n"
" if (i == 7) { break; };\n"
" };\n"
" return i;\n"
"};", 7 },
/* switch with multi-expr cases + default */
{ "fn classify(x: i32) i32 = {\n"
" switch (x) {\n"
" case 1, 2, 3: return 10;\n"
" case 10: return 99;\n"
" case: return 50;\n"
" };\n"
" return -1;\n"
"};\n"
"fn main() i32 = {\n"
" return classify(2) + classify(10) + classify(99);\n"
"};", 159 }, /* 10+99+50=159 */
/* fmt module: stdlib formatter for ints + strings */
{ "use fmt;\n"
"fn main() i32 = {\n"
" fmt.println(\"ww\");\n"
" fmt.printlnint(42);\n"
" fmt.printlnint(-7);\n"
" return 0;\n"
"};", 0 },
/* struct with i32 fields: MOVL/MOVSXD avoids clobbering neighbors */
{ "type point = struct { x: i32, y: i32 };\n"
"fn distsq(p: point) i32 = { return p.x * p.x + p.y * p.y; };\n"
"fn main() i32 = {\n"
" let p: point = point { x = 3, y = 4 };\n"
" return distsq(p);\n"
"};", 25 },
/* str equality via == and != */
{ "fn main() i32 = {\n"
" let a: str = \"hello\";\n"
" let b: str = \"hello\";\n"
" let c: str = \"world\";\n"
" let n: i32 = 0;\n"
" if (a == b) { n += 10; };\n"
" if (a != c) { n += 20; };\n"
" return n;\n"
"};", 30 },
/* CLAUDE.md's move pattern: ptr-to-struct compound field write */
{ "type point = struct { x: i32, y: i32 };\n"
"fn move(p: *point, dx: i32, dy: i32) void = {\n"
" p.x += dx; p.y += dy;\n"
"};\n"
"fn main() i32 = {\n"
" let pt: point = point { x = 0, y = 0 };\n"
" move(&pt, 3, 4);\n"
" return pt.x + pt.y;\n"
"};", 7 },
/* vtable polymorphism: struct of fn pointers, indirect call */
{ "type ops = struct { add: fn(a: i32, b: i32) i32 };\n"
"fn plus(a: i32, b: i32) i32 = { return a + b; };\n"
"fn main() i32 = {\n"
" let v: ops = ops { add = plus };\n"
" return v.add(20, 22);\n"
"};", 42 },
/* compound bitwise/shift assigns */
{ "fn main() i32 = {\n"
" let x: i32 = 100;\n"
" x &= 0x3f; x |= 0x80; x ^= 0xc4;\n"
" x *= 2; x <<= 1; x >>= 2;\n"
" return x;\n"
"};", 96 },
/* user-defined slice append via *[]u8: stdlib slices.appendu8.
* Demonstrates allocator + ptr-to-slice fields + scaled index write. */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let s: []u8;\n"
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
" slices.appendu8(&s, 65u8);\n"
" slices.appendu8(&s, 66u8);\n"
" slices.appendu8(&s, 67u8);\n"
" os.write(1, s.ptr, s.len: u64);\n"
" os.write(1, \"\\n\".ptr, 1u64);\n"
" return s.len;\n"
"};", 3 },
/* Hare-style builtins: append(s, v) and len(s). The compiler
* lowers these to slices.appendu8 / s.len access. */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let s: []u8;\n"
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
" append(s, 88u8); append(s, 89u8); append(s, 90u8);\n"
" os.write(1, s.ptr, len(s): u64);\n"
" os.write(1, \"\\n\".ptr, 1u64);\n"
" return len(s);\n"
"};", 3 },
/* str-returning function: 16-byte return via AX:DX (SysV). The
* caller's str slot is filled from those two regs. */
{ "use strings;\n"
"use fmt;\n"
"fn main() i32 = {\n"
" let r: str = strings.concat(\"hello, \", \"world\");\n"
" fmt.println(r);\n"
" return r.len;\n"
"};", 12 },
/* variadic append + static qualifier (Hare idiom) */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let s: []u8;\n"
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
" static append(s, 72u8, 105u8, 33u8, 10u8);\n"
" os.write(1, s.ptr, len(s): u64);\n"
" return len(s);\n"
"};", 4 },
/* alloc() builtin: heap-allocate a struct, init from struct-lit */
{ "use os;\n"
"type point = struct { x: i32, y: i32 };\n"
"fn main() i32 = {\n"
" let p: *point = alloc(point { x = 3, y = 4 });\n"
" return p.x * p.x + p.y * p.y;\n"
"};", 25 },
/* Hare-style range loop: for (let x .. slice) iterates elements */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let s: []u8;\n"
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
" append(s, 10u8, 20u8, 30u8, 40u8);\n"
" let total: i32 = 0;\n"
" for (let b .. s) { total += b: i32; };\n"
" return total;\n"
"};", 100 },
/* alloc([], n): fresh empty slice with cap n */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let s: []u8 = alloc([], 16);\n"
" append(s, 72u8, 105u8);\n"
" return s.cap;\n"
"};", 16 },
/* variadic spread: append(dst, src...) iterates src */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let src: []u8;\n"
" src.ptr = nil; src.len = 0; src.cap = 0;\n"
" append(src, 65u8, 66u8, 67u8);\n"
" let dst: []u8;\n"
" dst.ptr = nil; dst.len = 0; dst.cap = 0;\n"
" append(dst, src...);\n"
" os.write(1, dst.ptr, dst.len: u64);\n"
" os.write(1, \"\\n\".ptr, 1u64);\n"
" return dst.len;\n"
"};", 3 },
/* Hare-style tuple destructure in let */
{ "fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };\n"
"fn main() i32 = {\n"
" let (q, r) = divmod(17, 5);\n"
" return (q + r): i32;\n"
"};", 5 },
/* Hare-style tuple destructure in for-range */
{ "fn main() i32 = {\n"
" let buf: [4]i64;\n"
" buf[0] = 1; buf[1] = 10; buf[2] = 2; buf[3] = 20;\n"
" let s: [](i64, i64);\n"
" s.ptr = buf.ptr: *(i64, i64);\n"
" s.len = 2; s.cap = 2;\n"
" let total: i64 = 0;\n"
" for (let (k, v) .. s) { total += k + v; };\n"
" return total: i32;\n"
"};", 33 },
/* Hare-style tuple positional access: t.0, t.1 */
{ "fn pair() (i64, i64) = { return 10, 32; };\n"
"fn main() i32 = {\n"
" let t: (i64, i64) = pair();\n"
" return (t.0 + t.1): i32;\n"
"};", 42 },
/* Hare-style abort/assert + free() builtin */
{ "use os;\n"
"type point = struct { x: i64, y: i64 };\n"
"fn main() i32 = {\n"
" let p: *point = alloc(point { x = 7, y = 35 });\n"
" let r: i64 = p.x + p.y;\n"
" free(p);\n"
" os.assert(r == 42, \"sum mismatch\\n\");\n"
" return r: i32;\n"
"};", 42 },
/* 3-field tuple destructure in for-range */
{ "fn main() i32 = {\n"
" let buf: [3]i64;\n"
" buf[0] = 5; buf[1] = 7; buf[2] = 30;\n"
" let s: [](i64, i64, i64);\n"
" s.ptr = buf.ptr: *(i64, i64, i64);\n"
" s.len = 1; s.cap = 1;\n"
" let total: i64 = 0;\n"
" for (let (a, b, c) .. s) { total += a + b + c; };\n"
" return total: i32;\n"
"};", 42 },
/* Hare-style tagged union + match */
{ "fn parse(n: i64) (i64 | i32) = {\n"
" if (n < 0) { return 1: i32; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | i32) = parse(40);\n"
" let s: i64 = 0;\n"
" match (r) {\n"
" case let v: i64 => s = v;\n"
" case let e: i32 => s = -1;\n"
" };\n"
" return (s + 2): i32;\n"
"};", 42 },
/* ? propagation up the stack */
{ "fn try1(n: i64) (i64 | i32) = {\n"
" if (n < 0) { return 99: i32; };\n"
" return n;\n"
"};\n"
"fn try2(n: i64) (i64 | i32) = {\n"
" let v: i64 = try1(n)?;\n"
" return v + 100;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | i32) = try2(-1);\n"
" let s: i64 = 0;\n"
" match (r) {\n"
" case let v: i64 => s = v;\n"
" case let e: i32 => s = e: i64;\n"
" };\n"
" return s: i32;\n"
"};", 99 },
/* match cases written in reverse variant order — dispatch must
* use the variant tag, not the case position */
{ "fn parse(n: i64) (i64 | i32) = {\n"
" if (n < 0) { return 7: i32; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | i32) = parse(-1);\n"
" match (r) {\n"
" case let e: i32 => return e;\n"
" case let v: i64 => return (v + 1000): i32;\n"
" };\n"
" return 0;\n"
"};", 7 },
/* let-init from a bare variant value: tag must be synthesised */
{ "fn main() i32 = {\n"
" let r: (i64 | i32) = 7: i32;\n"
" match (r) {\n"
" case let v: i64 => return 1;\n"
" case let e: i32 => return e;\n"
" };\n"
" return 0;\n"
"};", 7 },
/* let-init from an untyped literal: variant inclusion must let
* the assignability check through, and the default variant wins */
{ "fn main() i32 = {\n"
" let r: (i64 | i32) = 5;\n"
" match (r) {\n"
" case let v: i64 => return v: i32;\n"
" case let e: i32 => return 99;\n"
" };\n"
" return 0;\n"
"};", 5 },
/* assignment to a tagged-union local: same tag synthesis */
{ "fn main() i32 = {\n"
" let r: (i64 | i32) = 0;\n"
" r = 9: i32;\n"
" match (r) {\n"
" case let v: i64 => return 1;\n"
" case let e: i32 => return e;\n"
" };\n"
" return 0;\n"
"};", 9 },
/* default arm `case =>` */
{ "fn parse(n: i64) (i64 | i32) = {\n"
" if (n < 0) { return 1: i32; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | i32) = parse(-1);\n"
" match (r) {\n"
" case let v: i64 => return 1;\n"
" case => return 7;\n"
" };\n"
" return 0;\n"
"};", 7 },
/* `case T =>` without binding still dispatches by tag */
{ "fn parse(n: i64) (i64 | i32) = {\n"
" if (n < 0) { return 1: i32; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | i32) = parse(40);\n"
" match (r) {\n"
" case i32 => return 1;\n"
" case let v: i64 => return v: i32;\n"
" };\n"
" return 0;\n"
"};", 40 },
/* str-typed variant payload: let-init with a string literal,
* match-binding loads ptr+len from the slot */
{ "fn main() i32 = {\n"
" let r: (i64 | str) = \"hello, world\";\n"
" match (r) {\n"
" case let n: i64 => return 1;\n"
" case let s: str => return s.len: i32;\n"
" };\n"
" return 0;\n"
"};", 12 },
/* assigning a string into a tagged-union local */
{ "fn main() i32 = {\n"
" let r: (i64 | str) = 0;\n"
" r = \"abc\";\n"
" match (r) {\n"
" case let n: i64 => return 1;\n"
" case let s: str => return s.len: i32;\n"
" };\n"
" return 0;\n"
"};", 3 },
/* fn returning (T | str) — wide return ABI */
{ "fn parse(n: i64) (i64 | str) = {\n"
" if (n < 0) { return \"negative number\"; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | str) = parse(-1);\n"
" match (r) {\n"
" case let n: i64 => return 1;\n"
" case let s: str => return s.len: i32;\n"
" };\n"
" return 0;\n"
"};", 15 },
/* ? propagating a str-typed error all the way up */
{ "fn try1(n: i64) (i64 | str) = {\n"
" if (n < 0) { return \"fail\"; };\n"
" return n;\n"
"};\n"
"fn try2(n: i64) (i64 | str) = {\n"
" let v: i64 = try1(n)?;\n"
" return v + 100;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | str) = try2(-1);\n"
" match (r) {\n"
" case let n: i64 => return n: i32;\n"
" case let s: str => return s.len: i32;\n"
" };\n"
" return 0;\n"
"};", 4 },
/* ? success unwrap when the first variant is itself str */
{ "fn make() (str | i64) = {\n"
" return \"ok\";\n"
"};\n"
"fn main() i32 = {\n"
" let r: (str | i64) = make();\n"
" let v: str = r?;\n"
" return v.len: i32;\n"
"};", 2 },
/* type error = str; named-alias variant works through the
* full happy/error path */
{ "type error = str;\n"
"fn read(n: i64) (i64 | error) = {\n"
" if (n < 0) { return \"eof\": error; };\n"
" return n + 1;\n"
"};\n"
"fn main() i32 = {\n"
" let r: (i64 | error) = read(-1);\n"
" match (r) {\n"
" case let v: i64 => return v: i32;\n"
" case let e: error => return e.len: i32;\n"
" };\n"
" return 0;\n"
"};", 3 },
/* multi-pattern arm: `case T1 | T2 =>` matches either tag */
{ "fn pick(n: i64) (i64 | i32 | u32) = {\n"
" if (n < 0) { return 1: i32; };\n"
" if (n == 0) { return 2: u32; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r1: (i64 | i32 | u32) = pick(0);\n"
" let r2: (i64 | i32 | u32) = pick(-1);\n"
" let r3: (i64 | i32 | u32) = pick(7);\n"
" let acc: i32 = 0;\n"
" match (r1) {\n"
" case let v: i64 => acc += 100;\n"
" case i32 | u32 => acc += 1;\n"
" };\n"
" match (r2) {\n"
" case let v: i64 => acc += 100;\n"
" case i32 | u32 => acc += 10;\n"
" };\n"
" match (r3) {\n"
" case let v: i64 => acc += v: i32;\n"
" case i32 | u32 => acc += 100;\n"
" };\n"
" return acc;\n"
"};", 18 },
/* Named-alias tagged union as fn arg + ≤16B variants */
{ "type result = (i64 | i32);\n"
"fn unwrap(r: result) i64 = {\n"
" match (r) {\n"
" case let v: i64 => return v;\n"
" case let e: i32 => return e: i64;\n"
" };\n"
" return -1;\n"
"};\n"
"fn main() i32 = {\n"
" let r1: result = 100;\n"
" let r2: result = 7: i32;\n"
" return (unwrap(r1) + unwrap(r2)): i32;\n"
"};", 107 },
/* 24B tagged-union arg with str variant */
{ "type result = (i64 | str);\n"
"fn classify(r: result) i32 = {\n"
" match (r) {\n"
" case let v: i64 => return 1;\n"
" case let e: str => return e.len: i32;\n"
" };\n"
" return -1;\n"
"};\n"
"fn main() i32 = {\n"
" let r1: result = \"hello\";\n"
" let r2: result = 42;\n"
" return classify(r1) + classify(r2);\n"
"};", 6 },
/* Tagged union as struct field — both literal init and assign,
* and match-on-field reads from the field's slot in place */
{ "type point = struct {\n"
" x: i32,\n"
" err: (i64 | str),\n"
"};\n"
"fn main() i32 = {\n"
" let p: point = point { x = 1, err = 0 };\n"
" p.err = \"updated\";\n"
" match (p.err) {\n"
" case let v: i64 => return 0;\n"
" case let e: str => return e.len: i32;\n"
" };\n"
" return -1;\n"
"};", 7 },
/* Pointer variant in a tagged union */
{ "type point = struct { x: i32, y: i32 };\n"
"fn main() i32 = {\n"
" let p: point = point { x = 3, y = 4 };\n"
" let r: (*point | str) = &p;\n"
" match (r) {\n"
" case let pp: *point => return pp.x + pp.y;\n"
" case let e: str => return -1;\n"
" };\n"
" return 0;\n"
"};", 7 },
/* Forwarding `return inner(n)` when both fns share a tagged-
* union return type — value passes through unwrapped */
{ "type result = (i64 | str);\n"
"fn inner(n: i64) result = {\n"
" if (n < 0) { return \"neg\"; };\n"
" return n + 1;\n"
"};\n"
"fn outer(n: i64) result = {\n"
" return inner(n);\n"
"};\n"
"fn main() i32 = {\n"
" let r: result = outer(-1);\n"
" match (r) {\n"
" case let v: i64 => return v: i32;\n"
" case let e: str => return e.len: i32;\n"
" };\n"
" return 0;\n"
"};", 3 },
/* match directly on a call expression (no intermediate let) */
{ "fn make(n: i64) (i64 | str) = {\n"
" if (n < 0) { return \"neg\"; };\n"
" return n + 1;\n"
"};\n"
"fn main() i32 = {\n"
" match (make(-1)) {\n"
" case let v: i64 => return v: i32;\n"
" case let e: str => return e.len: i32;\n"
" };\n"
" return 0;\n"
"};", 3 },
/* Plan 9-style sentinel error idiom: `def NAME: error = "lit"`
* inlines as the (ptr, len) pair at use sites. */
{ "type error = str;\n"
"def eEOF: error = \"eof\";\n"
"def eShortRead: error = \"short read\";\n"
"fn read(n: i64) (i64 | error) = {\n"
" if (n < 0) { return eEOF; };\n"
" if (n == 0) { return eShortRead; };\n"
" return n + 1;\n"
"};\n"
"fn main() i32 = {\n"
" let r0: (i64 | error) = read(0);\n"
" let r1: (i64 | error) = read(-1);\n"
" let r2: (i64 | error) = read(5);\n"
" let acc: i32 = 0;\n"
" match (r0) {\n"
" case let v: i64 => acc += 100;\n"
" case let e: error => acc += e.len: i32;\n"
" };\n"
" match (r1) {\n"
" case let v: i64 => acc += 100;\n"
" case let e: error => acc += e.len: i32;\n"
" };\n"
" match (r2) {\n"
" case let v: i64 => acc += v: i32;\n"
" case let e: error => acc += 100;\n"
" };\n"
" return acc;\n"
"};", 19 },
/* End-to-end stdlib usage: pull in lib/os and exercise the
* fallible API tryread/trywrite returning (i64 | str) over a
* real syscall. Validates that imported tagged-union returns
* survive the linker as well as the call ABI. */
{ "use os;\n"
"fn main() i32 = {\n"
" let buf: [3]u8;\n"
" buf[0] = 88: u8;\n"
" let ok: (i64 | str) = os.trywrite(1, buf.ptr, 1u64);\n"
" let bad: (i64 | str) = os.trywrite(999: i32, buf.ptr, 1u64);\n"
" let acc: i32 = 0;\n"
" match (ok) {\n"
" case let n: i64 => acc += n: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (bad) {\n"
" case let n: i64 => acc += -100;\n"
" case let e: str => acc += e.len: i32;\n"
" };\n"
" return acc;\n"
"};", 13 }, /* 1 byte written to fd 1, plus len(\"write failed\")=12 */
/* strconv.parse64: fallible signed decimal. Two successful
* parses contribute their values; one bad parse contributes
* the error message length (20 = len(\"parse: invalid digit\")). */
{ "use strconv;\n"
"fn main() i32 = {\n"
" let r1: (i64 | str) = strconv.parse64(\"42\");\n"
" let r2: (i64 | str) = strconv.parse64(\"-7\");\n"
" let r3: (i64 | str) = strconv.parse64(\"abc\");\n"
" let acc: i32 = 0;\n"
" match (r1) {\n"
" case let v: i64 => acc += v: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (r2) {\n"
" case let v: i64 => acc += v: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (r3) {\n"
" case let v: i64 => acc += -100;\n"
" case let e: str => acc += e.len: i32;\n"
" };\n"
" return acc;\n"
"};", 55 }, /* 42 + (-7) + 20 */
/* strconv.parseu64: success path 123, error path captures
* len(\"parse: invalid digit\") = 20 for the leading-sign reject. */
{ "use strconv;\n"
"fn main() i32 = {\n"
" let r1: (u64 | str) = strconv.parseu64(\"123\");\n"
" let r2: (u64 | str) = strconv.parseu64(\"-1\");\n"
" let acc: i32 = 0;\n"
" match (r1) {\n"
" case let v: u64 => acc += v: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (r2) {\n"
" case let v: u64 => acc += -100;\n"
" case let e: str => acc += e.len: i32;\n"
" };\n"
" return acc;\n"
"};", 143 }, /* 123 + 20 */
/* strings.indexbyte (Plan 9 -1) and strings.index (substring). */
{ "use strings;\n"
"fn main() i32 = {\n"
" let s: str = \"hello, world\";\n"
" let i1: i32 = strings.indexbyte(s, 44u8);\n"
" let i2: i32 = strings.indexbyte(s, 122u8);\n"
" let i3: i32 = strings.index(s, \"world\");\n"
" let i4: i32 = strings.index(s, \"nope\");\n"
" return i1 + i2 + i3 + i4;\n"
"};", 10 }, /* 5 + (-1) + 7 + (-1) */
/* bytes.indexsub: substring search over []u8. */
{ "use bytes;\n"
"fn main() i32 = {\n"
" let buf: [12]u8;\n"
" buf[0] = 104u8; buf[1] = 101u8; buf[2] = 108u8; buf[3] = 108u8;\n"
" buf[4] = 111u8; buf[5] = 44u8; buf[6] = 32u8; buf[7] = 119u8;\n"
" buf[8] = 111u8; buf[9] = 114u8; buf[10] = 108u8; buf[11] = 100u8;\n"
" let needle: [3]u8;\n"
" needle[0] = 119u8; needle[1] = 111u8; needle[2] = 114u8;\n"
" return bytes.indexsub(buf[0:12], needle[0:3]);\n"
"};", 7 },
/* errors.is — sentinel comparison through a (T | error) union.
* Sets up two errors, dispatches each, and confirms the matching
* sentinel detection. */
{ "use errors;\n"
"fn parse(n: i64) (i64 | errors.error) = {\n"
" if (n < 0) { return errors.eEOF; };\n"
" if (n == 0) { return errors.eShortRead; };\n"
" return n;\n"
"};\n"
"fn main() i32 = {\n"
" let r1: (i64 | errors.error) = parse(-1);\n"
" let r2: (i64 | errors.error) = parse(0);\n"
" let acc: i32 = 0;\n"
" match (r1) {\n"
" case let v: i64 => acc += -100;\n"
" case let e: errors.error =>\n"
" if (errors.is(e, errors.eEOF)) { acc += 1; }\n"
" else { acc += -100; };\n"
" };\n"
" match (r2) {\n"
" case let v: i64 => acc += -100;\n"
" case let e: errors.error =>\n"
" if (errors.is(e, errors.eShortRead)) { acc += 10; }\n"
" else { acc += -100; };\n"
" };\n"
" return acc;\n"
"};", 11 },
/* bufio.takeline: drain successive '\\n'-terminated lines from a
* pre-filled buffer, then a trailing fragment that returns the
* `linerr` variant carrying \"no newline\" (10 chars). */
{ "use bufio;\n"
"fn main() i32 = {\n"
" let raw: [11]u8;\n"
" raw[0] = 102u8; raw[1] = 111u8; raw[2] = 111u8; raw[3] = 10u8;\n"
" raw[4] = 98u8; raw[5] = 97u8; raw[6] = 114u8; raw[7] = 10u8;\n"
" raw[8] = 98u8; raw[9] = 97u8; raw[10] = 122u8;\n"
" let b: bufio.buf;\n"
" b.s = nil; b.data = raw.ptr; b.cap = 11; b.r = 0; b.w = 11;\n"
" let acc: i32 = 0;\n"
" let l1: (str | bufio.linerr) = bufio.takeline(&b);\n"
" match (l1) {\n"
" case let s: str => acc += s.len: i32;\n"
" case let e: bufio.linerr => acc += -100;\n"
" };\n"
" let l2: (str | bufio.linerr) = bufio.takeline(&b);\n"
" match (l2) {\n"
" case let s: str => acc += s.len: i32;\n"
" case let e: bufio.linerr => acc += -100;\n"
" };\n"
" let l3: (str | bufio.linerr) = bufio.takeline(&b);\n"
" match (l3) {\n"
" case let s: str => acc += -100;\n"
" case let e: bufio.linerr => acc += e.len: i32;\n"
" };\n"
" return acc;\n"
"};", 16 }, /* 3 + 3 + len(\"no newline\")=10 */
{ NULL, 0 }
};
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
/* Resolve to absolute path: tests chdir into /tmp/... */
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char src[64], exe[64];
snprintf(src, sizeof src, "/tmp/wwe2e_%d_%d.ww", getpid(), i);
snprintf(exe, sizeof exe, "/tmp/wwe2e_%d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
fputs(rows[i].src, f);
fclose(f);
char cmd[1024];
/* ww build writes the binary to the current working dir,
* named after the source basename. We override by chdir. */
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwe2e_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) { fail++; continue; }
char outbin[128];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n",
i, got, rows[i].want_exit, rows[i].src);
fail++;
}
unlink(src); unlink(outbin); rmdir(tmpdir);
(void)exe;
}
if (fail) { fprintf(stderr, "%d/%d e2e tests failed\n", fail, n); return 1; }
printf("e2e: %d/%d ok\n", n, n);
return 0;
}

80
test/wcc/800_ffi.c Normal file
View File

@@ -0,0 +1,80 @@
/*
* 800_ffi — verify the @symbol("X") attribute redirects CALLs at
* the codegen layer. The ww source declares a body-less fn with an
* external symbol name, calls it; the emitted .s must reference the
* external name, not the ww-side ident.
*
* Also smoke-tests building lib/c/libc/libc.ww (parse + check + cgen
* succeed; nothing to run).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static char *
run6c(const char *bin, const char *src)
{
char path[64];
snprintf(path, sizeof path, "/tmp/wwffi_%d.ww", getpid());
FILE *f = fopen(path, "wb");
fputs(src, f);
fclose(f);
char cmd[512];
snprintf(cmd, sizeof cmd, "%s/w6c %s 2>&1", bin, path);
FILE *p = popen(cmd, "r");
size_t cap = 4096, n = 0;
char *buf = malloc(cap);
int c;
while ((c = fgetc(p)) != EOF) {
if (n + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); }
buf[n++] = (char)c;
}
buf[n] = '\0';
pclose(p);
unlink(path);
return buf;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
int fail = 0;
/* @symbol redirects ident name to symbol name */
{
char *out = run6c(bin,
"@symbol(\"cout\") fn writeit(b: *u8, n: u64) i64;\n"
"fn main() i32 = { writeit(nil, 0); return 0; };");
if (strstr(out, "CALL\tcout(SB)") == NULL) {
fprintf(stderr, "ffi: missing CALL cout(SB) in:\n%s\n", out);
fail++;
}
if (strstr(out, "CALL\twriteit(SB)") != NULL) {
fprintf(stderr, "ffi: should not call by ident name:\n%s\n", out);
fail++;
}
free(out);
}
/* lib/c/libc parses + cgens (no defined fns means no TEXT directives) */
{
char path[256];
snprintf(path, sizeof path, "%s/../../lib/c/libc/libc.ww", bin);
char cmd[512];
snprintf(cmd, sizeof cmd, "%s/w6c %s > /dev/null 2>&1", bin, path);
int rc = system(cmd);
if (rc != 0) {
fprintf(stderr, "ffi: w6c failed on libc.ww (rc=%d)\n", rc);
fail++;
}
}
if (fail) return 1;
puts("ffi: ok");
return 0;
}

152
test/wcc/810_dyn.c Normal file
View File

@@ -0,0 +1,152 @@
/*
* 810_dyn — end-to-end test of w6l's dynamic linker.
*
* Drives `ww build -L /usr/lib -l c` over a small ww program that
* binds libc symbols via @symbol, then runs the produced binary and
* checks the exit status. Verifies:
*
* - the .so loader (dyn.c) reads ET_DYN and extracts exports
* - PLT/GOT generation and the JUMP_SLOT relocation
* - PT_INTERP/PT_DYNAMIC emission
* - symbol versioning (.gnu.version + .gnu.version_r) — clock_gettime
* has both GLIBC_2.2.5 (compat) and GLIBC_2.17 (default) on glibc;
* the loader requires the right .gnu.version_r entry to bind
* the default vDSO-aware impl.
*
* Skipped (passing trivially) on systems without /usr/lib/libc.so.6.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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 *src;
int want_exit;
};
static const struct row rows[] = {
/* 1. _exit via dynamically-linked libc. Exit code is the plumbing
* of choice — proves the PLT entry calls into libc.so.6. */
{ "@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
"export fn main() i32 = { libc_exit(42); return 0; };", 42 },
/* 2. Multiple dyn syms in one binary: write + _exit. */
{ "@symbol(\"write\") fn libc_write(fd: i32, b: *u8, n: u64) i64;\n"
"@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
"export fn main() i32 = {\n"
" libc_write(1, \"x\".ptr, 1u64);\n"
" libc_exit(7);\n"
" return 0;\n"
"};", 7 },
/* 3. Symbol versioning: clock_gettime. Without DT_VERSYM/VERNEED
* pointing at a Vernaux entry for GLIBC_2.17, the loader either
* picks the wrong impl or fails outright on modern glibc. */
{ "@symbol(\"clock_gettime\") fn clock_gettime(c: i32, ts: *u8) i32;\n"
"@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
"export fn main() i32 = {\n"
" let ts: [16]u8;\n"
" let r: i32 = clock_gettime(1, ts.ptr);\n" /* CLOCK_MONOTONIC */
" libc_exit(r);\n"
" return 0;\n"
"};", 0 },
/* 4. Take the address of an FFI binding and call it indirectly.
* Codegen must apply @symbol resolution at the LEAQ site (so the
* stub address is `write`, not the ww-side ident `libc_write`),
* and the local-variable call must be CALL *AX, not CALL fp(SB). */
{ "@symbol(\"write\") fn libc_write(fd: i32, b: *u8, n: u64) i64;\n"
"@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
"export fn main() i32 = {\n"
" let fp: fn(fd: i32, b: *u8, n: u64) i64 = libc_write;\n"
" fp(1, \"X\".ptr, 1u64);\n"
" libc_exit(5);\n"
" return 0;\n"
"};", 5 },
{ NULL, 0 }
};
int
main(void)
{
if (access("/usr/lib/libc.so.6", 0) != 0
&& access("/lib/x86_64-linux-gnu/libc.so.6", 0) != 0
&& access("/lib64/libc.so.6", 0) != 0) {
puts("dyn: no libc.so.6 on this system — skipping");
return 0;
}
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
/* Probe each common libc dir to find the right -L. */
const char *libdir = NULL;
if (access("/usr/lib/libc.so.6", 0) == 0) libdir = "/usr/lib";
else if (access("/lib/x86_64-linux-gnu/libc.so.6", 0) == 0) libdir = "/lib/x86_64-linux-gnu";
else if (access("/lib64/libc.so.6", 0) == 0) libdir = "/lib64";
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char src[80], exe[80], tmpdir[80];
snprintf(src, sizeof src, "/tmp/wwdyn_%d_%d.ww", getpid(), i);
snprintf(exe, sizeof exe, "/tmp/wwdyn_%d_%d", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwdyn_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
FILE *f = fopen(src, "wb");
fputs(rows[i].src, f);
fclose(f);
char cmd[1024];
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww build %s -L %s -l c",
tmpdir, bin, src, libdir);
if (runwait(cmd) != 0) {
fprintf(stderr, "dyn row %d: build failed\n", i);
fail++;
unlink(src); rmdir(tmpdir);
continue;
}
char outbin[160];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "dyn row %d: exit %d, want %d\n",
i, got, rows[i].want_exit);
fail++;
}
unlink(src); unlink(outbin); rmdir(tmpdir);
}
if (fail) {
fprintf(stderr, "%d/%d dyn tests failed\n", fail, n);
return 1;
}
printf("dyn: %d/%d ok\n", n, n);
return 0;
}

65
test/wcc/900_stdlib.c Normal file
View File

@@ -0,0 +1,65 @@
/*
* 900_stdlib — verify each stdlib module parses, type-checks, and
* codegens. We don't link or run them; they exist for downstream
* users to import. As real applications come online they will start
* exercising the bodies through the e2e harness.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static const char *modules[] = {
"lib/types/types.ww",
"lib/ascii/ascii.ww",
"lib/bytes/bytes.ww",
"lib/strings/strings.ww",
"lib/io/stream.ww",
"lib/errors/errors.ww",
"lib/os/os.ww",
"lib/strconv/strconv.ww",
"lib/sort/sort.ww",
"lib/path/path.ww",
"lib/encoding/utf8/utf8.ww",
"lib/encoding/hex/hex.ww",
"lib/hash/fnv/fnv.ww",
"lib/time/time.ww",
"lib/c/libc/libc.ww",
"lib/bufio/bufio.ww",
"lib/fmt/fmt.ww",
"lib/net/net.ww",
"lib/slices/slices.ww",
NULL
};
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
int n = 0, fail = 0;
for (int i = 0; modules[i]; i++, n++) {
char path[1024], cmd[2048];
snprintf(path, sizeof path, "%s/%s", cwd, modules[i]);
snprintf(cmd, sizeof cmd, "%s/w6c %s > /dev/null 2>&1",
bin, path);
int rc = system(cmd);
if (rc != 0) {
fprintf(stderr, "stdlib FAIL: %s (rc=%d)\n", modules[i], rc);
fail++;
}
}
if (fail) { fprintf(stderr, "%d/%d stdlib modules failed\n", fail, n); return 1; }
printf("stdlib: %d/%d ok\n", n, n);
return 0;
}

786
test/wcc/990_selfhost.c Normal file
View File

@@ -0,0 +1,786 @@
/*
* 990_selfhost — phase-10 marker test. Three probes:
*
* 1. The selfhost stubs (mem.ww, err.ww) must parse, typecheck and
* codegen via C-side `w6c`.
*
* 2. selfhost/test/smoke.ww — an actual ww program exercising the
* patterns the real port will use (bump arena, error union,
* struct-of-fn-pointer dispatch, byte scanner, strconv) — must
* build through `ww build` and exit 42 when run. Any non-42 exit
* names which probe broke (1..N).
*
* 3. `wwdump` is deterministic: running it twice on the same input
* must produce byte-identical output. This is the diff anchor for
* self-host. When the ww-side wwdump lands (task 3), the same
* bytes will have to come out of the ww frontend.
*
* The ceiling — `cmp ww2 ww3` — is gated on the full frontend port.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp(const char *path, char **outbuf, size_t *outlen)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
char *b = malloc((size_t)n + 1);
if (!b) { fclose(f); return -1; }
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = '\0';
fclose(f);
*outbuf = b;
*outlen = (size_t)n;
return 0;
}
/* Probe 1: each file must compile through `w6c`. */
static int
probe_codegen(const char *bin)
{
static const char *files[] = {
"selfhost/cmd/wcc/mem.ww",
"selfhost/cmd/wcc/err.ww",
NULL,
};
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
int fail = 0;
for (int i = 0; files[i]; i++) {
char cmd[2048];
snprintf(cmd, sizeof cmd,
"%s/w6c %s/%s > /dev/null 2>&1", bin, cwd, files[i]);
if (runwait(cmd) != 0) {
fprintf(stderr, "codegen FAIL: %s\n", files[i]);
fail++;
}
}
return fail ? -1 : 0;
}
/* Probe 2: end-to-end build+run of selfhost/test/smoke.ww. Exit 42 = ok. */
static int
probe_smoke(const char *bin)
{
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsh_%d", getpid());
mkdir(tmpdir, 0755);
char cmd[2048];
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww build %s/selfhost/test/smoke.ww >/dev/null 2>&1",
tmpdir, bin, cwd);
if (runwait(cmd) != 0) {
fprintf(stderr, "smoke FAIL: ww build did not succeed\n");
return -1;
}
char outbin[256];
snprintf(outbin, sizeof outbin, "%s/smoke", tmpdir);
int rc = runwait(outbin);
unlink(outbin);
char tmp[1024];
snprintf(tmp, sizeof tmp, "%s/smoke.s", tmpdir); unlink(tmp);
snprintf(tmp, sizeof tmp, "%s/smoke.o", tmpdir); unlink(tmp);
snprintf(tmp, sizeof tmp, "%s/smoke.combined.ww", tmpdir); unlink(tmp);
rmdir(tmpdir);
if (rc != 42) {
fprintf(stderr, "smoke FAIL: exit=%d (want 42; the number "
"names which probe in selfhost/test/smoke.ww broke)\n", rc);
return -1;
}
return 0;
}
/* Probe 3a: ww-side wwdump_ww must produce byte-identical output to
* the C-side wwdump on every fixture. The token-stream diff (-t) is
* the strongest signal: the lex.ww port has converged when this is
* empty across realistic source. The AST diff (-a) is a partial
* signal — the ww-side parser is currently a stub that handles only
* `use` clauses, so we exercise it on a stripped fixture. As parse.ww
* grows, more files will be added to the -a list. */
static int
probe_dump_diff_mode(const char *bin, const char *flag, const char **inputs)
{
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
int fail = 0;
for (int i = 0; inputs[i]; i++) {
char a[256], b[256], cmd[2048];
snprintf(a, sizeof a, "/tmp/wwd_diff_%d_c", getpid());
snprintf(b, sizeof b, "/tmp/wwd_diff_%d_w", getpid());
snprintf(cmd, sizeof cmd, "%s/wwdump %s %s/%s > %s 2>/dev/null",
bin, flag, cwd, inputs[i], a);
runwait(cmd);
snprintf(cmd, sizeof cmd, "%s/wwdump_ww %s %s/%s > %s 2>/dev/null",
bin, flag, cwd, inputs[i], b);
runwait(cmd);
char *ba = NULL, *bb = NULL;
size_t na = 0, nb = 0;
int rc1 = slurp(a, &ba, &na);
int rc2 = slurp(b, &bb, &nb);
if (rc1 < 0 || rc2 < 0) {
fprintf(stderr, "diff FAIL (%s): cannot read dumps for %s\n",
flag, inputs[i]);
fail++;
} else if (na != nb || memcmp(ba, bb, na) != 0) {
fprintf(stderr, "diff FAIL (%s): %s — C %zu bytes vs ww %zu bytes\n",
flag, inputs[i], na, nb);
fail++;
}
free(ba); free(bb);
unlink(a); unlink(b);
}
return fail ? -1 : 0;
}
static int
probe_dump_diff(const char *bin)
{
const char *tok_inputs[] = {
"selfhost/cmd/wcc/mem.ww",
"selfhost/cmd/wcc/err.ww",
"selfhost/cmd/wcc/lex.ww",
"selfhost/cmd/wcc/tok.ww",
"selfhost/cmd/wcc/ast.ww",
"selfhost/cmd/wcc/parse.ww",
"selfhost/cmd/wwdump/main.ww",
"selfhost/test/smoke.ww",
NULL,
};
/* AST diff coverage. The ww-side parser handles use/def/type/let/fn
* (with bodies), expressions with full Pratt precedence, statements
* (block/let/return/if/for/defer/break/continue), match arms,
* tagged unions, struct literals, attributes, and tuples. We diff
* against the C parser on every selfhost file plus the stdlib. */
const char *ast_inputs[] = {
"selfhost/test/uses.ww",
"selfhost/cmd/wcc/err.ww",
"selfhost/cmd/wcc/mem.ww",
"selfhost/cmd/wcc/lex.ww",
"selfhost/cmd/wcc/tok.ww",
"selfhost/cmd/wcc/ast.ww",
"selfhost/cmd/wcc/parse.ww",
"selfhost/cmd/wcc/typ.ww",
"selfhost/cmd/wcc/sym.ww",
"selfhost/cmd/wcc/check.ww",
"selfhost/cmd/wcc/cgen.ww",
"selfhost/cmd/wwdump/main.ww",
"selfhost/test/smoke.ww",
"lib/strconv/strconv.ww",
"lib/os/os.ww",
"lib/ascii/ascii.ww",
"lib/fmt/fmt.ww",
"lib/strings/strings.ww",
"lib/bytes/bytes.ww",
"lib/io/stream.ww",
"lib/errors/errors.ww",
"lib/bufio/bufio.ww",
"lib/types/types.ww",
"lib/sort/sort.ww",
"lib/path/path.ww",
"lib/slices/slices.ww",
"lib/net/net.ww",
"lib/encoding/utf8/utf8.ww",
"lib/encoding/hex/hex.ww",
"lib/hash/fnv/fnv.ww",
"lib/time/time.ww",
"lib/c/libc/libc.ww",
NULL,
};
int fail = 0;
if (probe_dump_diff_mode(bin, "-t", tok_inputs) != 0) fail++;
if (probe_dump_diff_mode(bin, "-a", ast_inputs) != 0) fail++;
return fail ? -1 : 0;
}
/* Probe 5: ww-side cgen (wwdump_ww -c) emits Plan 9 amd64 asm. For
* each fixture we (a) diff the ww asm against C-side w6c and (b)
* assemble + link + run the ww output and compare exit codes. Both
* must succeed. The cgen port handles literals, +/-/star/slash,
* compound assignment, idents, calls, if/else, for loops, and
* comparisons; fixtures stay within that surface. */
struct cprog { const char *src; int want_exit; };
static int
probe_ww_compile(const char *bin)
{
static const struct cprog progs[] = {
{ "fn main() i32 = { return 42; };", 42 },
{ "fn add(a: i32, b: i32) i32 = { return a + b; };\n"
"fn main() i32 = { return add(7, 35); };", 42 },
{ "fn sum(n: i32) i32 = {\n"
" let s: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < n) { s += i; i += 1; };\n"
" return s;\n"
"};\n"
"fn main() i32 = { return sum(10); };", 45 },
{ "fn check(x: i32) i32 = {\n"
" if (x > 0) { return 1; };\n"
" if (x < 0) { return 100; };\n"
" return 0;\n"
"};\n"
"fn main() i32 = { return check(7); };", 1 },
{ "fn fact(n: i32) i32 = {\n"
" if (n <= 1) { return 1; };\n"
" return n * fact(n - 1);\n"
"};\n"
"fn main() i32 = { return fact(5); };", 120 },
/* hello-world via syscall: exercises @symbol FFI, string
* literal interning, DATA emission, str struct slot (16B),
* str pseudo-fields (.ptr/.len), N_CAST. write(1,"hi\n",3)
* returns 3 — we cast that down to the exit code. */
{ "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n"
"fn main() i32 = {\n"
" let s: str = \"hi\\n\";\n"
" let r: i64 = rt_syscall(1, 1, s.ptr: i64, s.len: i64);\n"
" return r: i32;\n"
"};", 3 },
/* struct fields (local + via *struct), &local, struct-name
* registry: distance squared returns 25 = 3*3 + 4*4. */
{ "type point = struct { x: i64, y: i64 };\n"
"fn distance_sq(p: *point) i64 = { return p.x * p.x + p.y * p.y; };\n"
"fn main() i32 = {\n"
" let p: point;\n"
" p.x = 3i64;\n"
" p.y = 4i64;\n"
" return distance_sq(&p): i32;\n"
"};", 25 },
/* array indexing — element-size scaling for u8 arrays.
* Sets buf[0..3] to 7,8,9,10 then sums them: 34. */
{ "fn main() i32 = {\n"
" let buf: [4]u8;\n"
" buf[0] = 7u8;\n"
" buf[1] = 8u8;\n"
" buf[2] = 9u8;\n"
" buf[3] = 10u8;\n"
" let s: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < 4) { s += buf[i]: i32; i += 1; };\n"
" return s;\n"
"};", 34 },
/* struct literal init — `let p: point = point { x=..., y=...};`.
* Each field stored at its struct offset in the slot. */
{ "type point = struct { x: i64, y: i64 };\n"
"fn main() i32 = {\n"
" let p: point = point { x = 7i64, y = 35i64 };\n"
" return (p.x + p.y): i32;\n"
"};", 42 },
/* fn-returning-str — exercises the SysV (AX, DX) → (AX, BX)
* shuffle so str values flow through cgen as the canonical
* pair. write(\"hello world\\n\") → exit 12 (length). */
{ "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n"
"fn greeting() str = { return \"hello world\\n\"; };\n"
"fn main() i32 = {\n"
" let g: str = greeting();\n"
" rt_syscall(1, 1, g.ptr: i64, g.len: i64);\n"
" return g.len: i32;\n"
"};", 12 },
/* fn-pointer field call — `w.emit(b)` where w is a struct
* with an emit: fn(b: u8) i32 field. Loads the field value
* into AX and CALLs through it. The polymorphism shape ww
* uses instead of interfaces. */
{ "type writer = struct { emit: fn(b: u8) i32 };\n"
"fn count_emit(b: u8) i32 = { return b: i32 + 1; };\n"
"fn main() i32 = {\n"
" let w: writer = writer { emit = count_emit };\n"
" return w.emit(41u8);\n"
"};", 42 },
/* Match expression — tagged union dispatch. classify(ok)=42,
* classify(err)=-1, sum 41. Exercises tagged init, tagged-arg
* push (3 regs), match arm tag-compare + payload bind. */
{ "fn classify(r: (i32 | str)) i32 = {\n"
" match (r) {\n"
" case let v: i32 => return v;\n"
" case let e: str => return -1;\n"
" };\n"
" return 0;\n"
"};\n"
"fn main() i32 = {\n"
" let ok: (i32 | str) = 42;\n"
" let err: (i32 | str) = \"fail\";\n"
" return classify(ok) + classify(err);\n"
"};", 41 },
/* Compound subtract via *struct field — regression for
* the bug that made wwdump_ww segfault on its own source.
* c.x = 5; dec; dec; dec → expect 2. Pre-fix, both C and
* ww emitted SUBQ in the wrong direction (computing rhs-old
* instead of old-rhs) and this returned -4 (= 252 as u8). */
{ "type ctx = struct { x: i32 };\n"
"fn dec(c: *ctx) void = { c.x -= 1; };\n"
"fn main() i32 = {\n"
" let c: ctx;\n"
" c.x = 5;\n"
" dec(&c); dec(&c); dec(&c);\n"
" return c.x;\n"
"};", 2 },
/* Chained N_DOT: o.ptr_to_inner.val (read field through a
* pointer-to-struct field). Pre-fix the cgen fell through
* silently and returned the inner pointer instead of the
* dereferenced field. Surfaced porting w6l/pass.ww. */
{ "@symbol(\"rt_alloc\") fn rt_alloc(n: u64) *void;\n"
"type inner = struct { val: u64 };\n"
"type outer = struct { p: *inner };\n"
"fn main() i32 = {\n"
" let in_: *inner = rt_alloc(8u64): *inner;\n"
" in_.val = 42u64;\n"
" let o: *outer = rt_alloc(8u64): *outer;\n"
" o.p = in_;\n"
" return o.p.val: i32;\n"
"};", 42 },
{ NULL, 0 },
};
char rt[1024];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
int fail = 0;
for (int i = 0; progs[i].src; i++) {
char src[64], wws[64], cs[64], obj[64], exe[64], cmd[2048];
snprintf(src, sizeof src, "/tmp/wwc_%d_%d.ww", getpid(), i);
snprintf(wws, sizeof wws, "/tmp/wwc_%d_%d_w.s", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wwc_%d_%d_c.s", getpid(), i);
snprintf(obj, sizeof obj, "/tmp/wwc_%d_%d.o", getpid(), i);
snprintf(exe, sizeof exe, "/tmp/wwc_%d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) { fail++; continue; }
fputs(progs[i].src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/wwdump_ww -c %s > %s 2>/dev/null",
bin, src, wws);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-compile prog %d: wwdump_ww -c errored\n", i);
fail++; goto cleanup;
}
snprintf(cmd, sizeof cmd, "%s/w6c %s > %s 2>/dev/null", bin, src, cs);
runwait(cmd);
char *bw = NULL, *bc = NULL;
size_t nw = 0, nc = 0;
if (slurp(wws, &bw, &nw) >= 0 && slurp(cs, &bc, &nc) >= 0) {
if (nw != nc || memcmp(bw, bc, nw) != 0) {
fprintf(stderr, "ww-compile prog %d: asm differs (C %zu, ww %zu)\n",
i, nc, nw);
fail++;
}
}
free(bw); free(bc);
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, obj, wws);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-compile prog %d: w6a failed\n", i);
fail++; goto cleanup;
}
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null",
bin, exe, obj, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-compile prog %d: w6l failed\n", i);
fail++; goto cleanup;
}
int rc = runwait(exe);
if (rc != progs[i].want_exit) {
fprintf(stderr, "ww-compile prog %d: exit=%d, want %d\n",
i, rc, progs[i].want_exit);
fail++;
}
cleanup:
unlink(src); unlink(wws); unlink(cs); unlink(obj); unlink(exe);
}
return fail ? -1 : 0;
}
/* Probe 4: ww-side checker (wwdump_ww -r) runs name resolution on
* each "complete" selfhost fixture and reports zero unresolved. The
* complete set is the ones whose identifiers are all locally defined
* — they don't import other selfhost modules. (Files that DO import
* still resolve most names but have some unresolved type/sym refs;
* those are exercised by the broader -t and -a diff probes.) */
static int
probe_resolve(const char *bin)
{
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
const char *complete[] = {
"selfhost/cmd/wcc/err.ww",
"selfhost/cmd/wcc/mem.ww",
"selfhost/cmd/wcc/tok.ww",
"selfhost/test/smoke.ww",
NULL,
};
int fail = 0;
for (int i = 0; complete[i]; i++) {
char a[256], cmd[2048];
snprintf(a, sizeof a, "/tmp/wwd_r_%d", getpid());
snprintf(cmd, sizeof cmd, "%s/wwdump_ww -r %s/%s > %s 2>/dev/null",
bin, cwd, complete[i], a);
int rc = runwait(cmd);
if (rc != 0) {
fprintf(stderr, "resolve FAIL: %s exited %d (unresolved names)\n",
complete[i], rc);
fail++;
}
unlink(a);
}
return fail ? -1 : 0;
}
/* Probe 3: wwdump must be deterministic across two runs. */
static int
probe_dump_stable(const char *bin)
{
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
const char *inputs[] = {
"selfhost/cmd/wcc/mem.ww",
"selfhost/cmd/wcc/err.ww",
"selfhost/test/smoke.ww",
NULL,
};
const char *modes[] = { "-t", "-a", NULL };
int fail = 0;
for (int i = 0; inputs[i]; i++) {
for (int m = 0; modes[m]; m++) {
char a[256], b[256];
snprintf(a, sizeof a, "/tmp/wwd_%d_a", getpid());
snprintf(b, sizeof b, "/tmp/wwd_%d_b", getpid());
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/wwdump %s %s/%s > %s 2>/dev/null",
bin, modes[m], cwd, inputs[i], a);
if (runwait(cmd) != 0) {
fprintf(stderr, "wwdump FAIL: %s %s\n", modes[m], inputs[i]);
unlink(a);
fail++;
continue;
}
snprintf(cmd, sizeof cmd, "%s/wwdump %s %s/%s > %s 2>/dev/null",
bin, modes[m], cwd, inputs[i], b);
runwait(cmd);
char *ba = NULL, *bb = NULL;
size_t na = 0, nb = 0;
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
fprintf(stderr, "wwdump FAIL: cannot read dumps\n");
free(ba); free(bb);
unlink(a); unlink(b);
fail++;
continue;
}
if (na == 0) {
fprintf(stderr, "wwdump FAIL: empty dump for %s %s\n",
modes[m], inputs[i]);
fail++;
} else if (na != nb || memcmp(ba, bb, na) != 0) {
fprintf(stderr, "wwdump FAIL: nondeterministic %s %s\n",
modes[m], inputs[i]);
fail++;
}
free(ba); free(bb);
unlink(a); unlink(b);
}
}
return fail ? -1 : 0;
}
/* Probe 9: full bootstrap fixed-point. ww1 → ww2 → ww3 with
* cmp ww2.s == ww3.s and cmp ww2 == ww3 (byte-identical binaries).
* This is the textbook self-host gate: the compiler must reach a
* fixed point under iterated self-compilation. */
static int
probe_bootstrap_fixed_point(const char *bin)
{
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfp_%d", getpid());
mkdir(tmpdir, 0755);
char ww2s[512], ww2o[512], ww2e[512];
char ww3s[512], ww3o[512], ww3e[512];
char rt[1024], cmd[4096];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
snprintf(ww2s, sizeof ww2s, "%s/ww2.s", tmpdir);
snprintf(ww2o, sizeof ww2o, "%s/ww2.o", tmpdir);
snprintf(ww2e, sizeof ww2e, "%s/ww2", tmpdir);
snprintf(ww3s, sizeof ww3s, "%s/ww3.s", tmpdir);
snprintf(ww3o, sizeof ww3o, "%s/ww3.o", tmpdir);
snprintf(ww3e, sizeof ww3e, "%s/ww3", tmpdir);
int fail = 0;
/* ww1 -> ww2 */
snprintf(cmd, sizeof cmd,
"%s/wwdump_ww -c %s/selfhost/cmd/wwdump/main.combined.ww > %s 2>/dev/null",
bin, cwd, ww2s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww1 self-compile\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, ww2o, ww2s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6a ww2.s\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null", bin, ww2e, ww2o, rt);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6l ww2\n"); fail++; goto cleanup; }
/* ww2 -> ww3 */
snprintf(cmd, sizeof cmd,
"%s -c %s/selfhost/cmd/wwdump/main.combined.ww > %s 2>/dev/null",
ww2e, cwd, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww2 self-compile\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, ww3o, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6a ww3.s\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null", bin, ww3e, ww3o, rt);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6l ww3\n"); fail++; goto cleanup; }
/* The fixed-point check: ww2.s == ww3.s, ww2 == ww3. */
snprintf(cmd, sizeof cmd, "cmp -s %s %s", ww2s, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww2.s != ww3.s (no fixed point)\n"); fail++; }
snprintf(cmd, sizeof cmd, "cmp -s %s %s", ww2e, ww3e);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww2 != ww3 binaries\n"); fail++; }
cleanup:
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
runwait(cmd);
return fail ? -1 : 0;
}
/* Probe 8: ww1 → ww2 ladder. wwdump_ww (= ww1, built by C tools)
* compiles its own concatenated source. The output assembles + links
* via w6a/w6l into ww2 (a fresh ww-built compiler binary). Confirms
* the full ww-cgen pipeline is functionally correct end-to-end on
* the entire frontend (lex+parse+check+cgen+ast+typ+sym+mem+err+
* driver). Pre-fix-set this segfaulted at 5919 lines of asm; post-
* fix-set it produces 43K+ lines that assemble/link cleanly. */
static int
probe_ww1_to_ww2(const char *bin)
{
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ww12_%d", getpid());
mkdir(tmpdir, 0755);
char asm_p[512], obj_p[512], exe_p[512], cmd[4096];
snprintf(asm_p, sizeof asm_p, "%s/ww2.s", tmpdir);
snprintf(obj_p, sizeof obj_p, "%s/ww2.o", tmpdir);
snprintf(exe_p, sizeof exe_p, "%s/ww2", tmpdir);
int fail = 0;
snprintf(cmd, sizeof cmd,
"%s/wwdump_ww -c %s/selfhost/cmd/wwdump/main.combined.ww > %s 2>/dev/null",
bin, cwd, asm_p);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww1->ww2 FAIL: wwdump_ww segfaulted on its own source\n");
fail++;
goto cleanup;
}
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, obj_p, asm_p);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww1->ww2 FAIL: w6a errored on ww1 output\n");
fail++;
goto cleanup;
}
char rt[1024];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null",
bin, exe_p, obj_p, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww1->ww2 FAIL: w6l errored\n");
fail++;
goto cleanup;
}
struct stat st;
if (stat(exe_p, &st) != 0 || !(st.st_mode & 0111)) {
fprintf(stderr, "ww1->ww2 FAIL: ww2 binary not executable\n");
fail++;
}
cleanup:
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
runwait(cmd);
return fail ? -1 : 0;
}
/* Probe 7: end-to-end via wwdump_ww. Use `ww build` to produce the
* concatenated source as a side effect, then drive ww-cgen → w6a →
* w6l → run, comparing the binary's exit code to the fixture's
* declared expectation. This is the bootstrap-grade signal: it
* doesn't require ww-cgen output to be byte-identical to C-cgen,
* only to be functionally correct. The byte-match probe above is
* useful while it lasts but layout choices (slot allocation order,
* spill placement) eventually diverge between cgens; correctness is
* the test that scales. */
static int
probe_ww_links(const char *bin)
{
const char *fixtures[][2] = {
{ "selfhost/test/sym_link.ww", "42" }, /* sym/typ/ast/mem */
{ NULL, NULL },
};
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
int fail = 0;
for (int i = 0; fixtures[i][0]; i++) {
const char *fix = fixtures[i][0];
int want = atoi(fixtures[i][1]);
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwlnk_%d_%d", getpid(), i);
mkdir(tmpdir, 0755);
const char *base = strrchr(fix, '/');
base = base ? base + 1 : fix;
char stem[256];
snprintf(stem, sizeof stem, "%s/%s", tmpdir, base);
char *dot = strrchr(stem, '.');
if (dot) *dot = '\0';
char tmpsrc[512];
snprintf(tmpsrc, sizeof tmpsrc, "%s.ww", stem);
char cmd[4096];
snprintf(cmd, sizeof cmd, "cp %s/%s %s", cwd, fix, tmpsrc);
runwait(cmd);
/* ww build to get the .combined.ww as a side effect. */
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww build -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1",
tmpdir, bin, cwd, tmpsrc);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: ww build %s\n", fix);
fail++;
goto cleanup;
}
/* Now compile the combined source via the ww frontend. */
char combinedp[512], wsp[512], wop[512], wexep[512];
snprintf(combinedp, sizeof combinedp, "%s.combined.ww", stem);
snprintf(wsp, sizeof wsp, "%s_w.s", stem);
snprintf(wop, sizeof wop, "%s_w.o", stem);
snprintf(wexep, sizeof wexep, "%s_w", stem);
snprintf(cmd, sizeof cmd,
"%s/wwdump_ww -c %s > %s 2>/dev/null", bin, combinedp, wsp);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: wwdump_ww -c %s\n", fix);
fail++;
goto cleanup;
}
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null",
bin, wop, wsp);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: w6a %s\n", fix);
fail++;
goto cleanup;
}
char rt[1024];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null",
bin, wexep, wop, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: w6l %s\n", fix);
fail++;
goto cleanup;
}
int rc = runwait(wexep);
if (rc != want) {
fprintf(stderr, "ww-links FAIL: %s exit=%d want=%d\n",
fix, rc, want);
fail++;
}
cleanup:
/* Sweep tmpdir contents, then remove. ww build also drops a
* C-built binary at <stem> — clean it too. */
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
runwait(cmd);
}
return fail ? -1 : 0;
}
/* Probe 6: ww-cgen byte-matches C-cgen on every selfhost file that
* raw w6c can compile in isolation (no driver concatenation). The list
* is the convergence set: when this stays empty, the cmp ww2 ww3
* ceiling becomes feasible. */
static int
probe_cgen_match(const char *bin)
{
const char *files[] = {
"selfhost/cmd/wcc/mem.ww",
"selfhost/cmd/wcc/err.ww",
"selfhost/cmd/wcc/tok.ww",
"selfhost/test/smoke.ww",
NULL,
};
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
int fail = 0;
for (int i = 0; files[i]; i++) {
char a[256], b[256], cmd[2048];
snprintf(a, sizeof a, "/tmp/wwd_cm_%d_c", getpid());
snprintf(b, sizeof b, "/tmp/wwd_cm_%d_w", getpid());
snprintf(cmd, sizeof cmd, "%s/w6c %s/%s > %s 2>/dev/null",
bin, cwd, files[i], a);
runwait(cmd);
snprintf(cmd, sizeof cmd, "%s/wwdump_ww -c %s/%s > %s 2>/dev/null",
bin, cwd, files[i], b);
runwait(cmd);
char *bc = NULL, *bw = NULL;
size_t nc = 0, nw = 0;
int rc1 = slurp(a, &bc, &nc);
int rc2 = slurp(b, &bw, &nw);
if (rc1 < 0 || rc2 < 0) {
fprintf(stderr, "cgen-match FAIL: read %s\n", files[i]);
fail++;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "cgen-match FAIL: %s — C %zu vs ww %zu bytes\n",
files[i], nc, nw);
fail++;
}
free(bc); free(bw);
unlink(a); unlink(b);
}
return fail ? -1 : 0;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
int fail = 0;
if (probe_codegen(bin) != 0) fail++;
if (probe_smoke(bin) != 0) fail++;
if (probe_dump_stable(bin) != 0) fail++;
if (probe_dump_diff(bin) != 0) fail++;
if (probe_resolve(bin) != 0) fail++;
if (probe_ww_compile(bin) != 0) fail++;
if (probe_cgen_match(bin) != 0) fail++;
if (probe_ww_links(bin) != 0) fail++;
if (probe_ww1_to_ww2(bin) != 0) fail++;
if (probe_bootstrap_fixed_point(bin) != 0) fail++;
if (fail) {
fprintf(stderr, "selfhost: %d probe(s) failed\n", fail);
return 1;
}
printf("selfhost: codegen ok; smoke=42; wwdump stable; "
"ww lexer matches C on 8 fixtures (-t); "
"ww parser matches C on 32 fixtures (-a); "
"ww checker resolves 4 self-contained fixtures (-r); "
"ww cgen compiles + runs 13 ww programs (incl. compound -= regression); "
"ww cgen byte-matches C on 4 selfhost fixtures (smoke/mem/err/tok); "
"ww cgen builds runnable binaries from sym/typ/ast/mem stack; "
"ww1 -> ww2 -> ww3 fixed-point reached: ww2.s == ww3.s, ww2 == ww3 byte-identical\n");
return 0;
}

135
test/wcc/991_w6a_ww.c Normal file
View File

@@ -0,0 +1,135 @@
/*
* 991_w6a_ww — phase-10 marker for the ww-side w6a port.
*
* Diffs the C-built `w6a` against the ww-built `w6a_ww` on a corpus of
* .s files. Both must produce byte-identical ELF .o output. The corpus
* spans:
* - rt/X.s hand-written runtime; small, varied operands
* - generated .s from the existing selfhost cgen pipeline
* (wwdump/w6a/w6l main.s) - large, exercises
* every encoding path w6c emits
*
* Any divergence is a port bug in selfhost/cmd/w6a/.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp(const char *path, char **outbuf, size_t *outlen)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
char *b = malloc((size_t)n + 1);
if (!b) { fclose(f); return -1; }
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = '\0';
fclose(f);
*outbuf = b;
*outlen = (size_t)n;
return 0;
}
static int
diff_one(const char *bin, const char *src)
{
char co[64], wo[64], cmd[2048];
snprintf(co, sizeof co, "/tmp/wwa_%d_c.o", getpid());
snprintf(wo, sizeof wo, "/tmp/wwa_%d_w.o", getpid());
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, co, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6a_ww FAIL: C w6a errored on %s\n", src);
unlink(co);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6a_ww -o %s %s 2>/dev/null", bin, wo, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6a_ww FAIL: ww w6a errored on %s\n", src);
unlink(co); unlink(wo);
return -1;
}
char *bc = NULL, *bw = NULL;
size_t nc = 0, nw = 0;
int rc = 0;
if (slurp(co, &bc, &nc) < 0 || slurp(wo, &bw, &nw) < 0) {
fprintf(stderr, "w6a_ww FAIL: cannot read .o for %s\n", src);
rc = -1;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "w6a_ww FAIL: %s — C %zu vs ww %zu bytes\n",
src, nc, nw);
rc = -1;
}
free(bc); free(bw);
unlink(co); unlink(wo);
return rc;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
const char *files[] = {
/* hand-written runtime — small, covers MOVQ/LEAQ/CALL/SYSCALL/RET. */
"rt/start.s",
"rt/syscall.s",
"rt/abort.s",
"rt/alloc.s",
"rt/streq.s",
/* generated .s from the existing selfhost stack — large,
* exercises every encoding w6c emits (the only way to hit
* the long tail of operand shapes). */
"selfhost/cmd/wwdump/main.s",
"selfhost/cmd/w6l/main.s",
"selfhost/cmd/w6a/main.s",
NULL,
};
int fail = 0;
int n = 0;
for (int i = 0; files[i]; i++) {
char p[2048];
snprintf(p, sizeof p, "%s/%s", cwd, files[i]);
if (diff_one(bin, p) != 0) fail++;
n++;
}
if (fail) {
fprintf(stderr, "w6a_ww: %d/%d diff(s) failed\n", fail, n);
return 1;
}
printf("w6a_ww: byte-identical to C w6a on %d corpus files "
"(rt/*.s + selfhost generated main.s)\n", n);
return 0;
}

137
test/wcc/992_w6l_ww.c Normal file
View File

@@ -0,0 +1,137 @@
/*
* 992_w6l_ww — phase-10 marker for the ww-side w6l port.
*
* Diffs the C-built `w6l` against the ww-built `w6l_ww` on a corpus of
* link inputs. Both must produce byte-identical static ELF binaries.
* The corpus mixes:
* - a single .o input (no archive resolution)
* - a .o + libwwrt.a link, exercising the SysV `ar` two-pass loader
*
* Any divergence is a port bug in selfhost/cmd/w6l/.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp(const char *path, char **outbuf, size_t *outlen)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
char *b = malloc((size_t)n + 1);
if (!b) { fclose(f); return -1; }
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = '\0';
fclose(f);
*outbuf = b;
*outlen = (size_t)n;
return 0;
}
/* link `inputs` (space-separated, the caller's responsibility to quote)
* with both linkers, compare bytes. */
static int
diff_one(const char *bin, const char *label, const char *inputs)
{
char co[64], wo[64], cmd[4096];
snprintf(co, sizeof co, "/tmp/wwl_%d_c", getpid());
snprintf(wo, sizeof wo, "/tmp/wwl_%d_w", getpid());
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s 2>/dev/null", bin, co, inputs);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6l_ww FAIL: C w6l errored on %s\n", label);
unlink(co);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6l_ww -o %s %s 2>/dev/null", bin, wo, inputs);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6l_ww FAIL: ww w6l errored on %s\n", label);
unlink(co); unlink(wo);
return -1;
}
char *bc = NULL, *bw = NULL;
size_t nc = 0, nw = 0;
int rc = 0;
if (slurp(co, &bc, &nc) < 0 || slurp(wo, &bw, &nw) < 0) {
fprintf(stderr, "w6l_ww FAIL: cannot read output for %s\n", label);
rc = -1;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "w6l_ww FAIL: %s — C %zu vs ww %zu bytes\n",
label, nc, nw);
rc = -1;
}
free(bc); free(bw);
unlink(co); unlink(wo);
return rc;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
struct {
const char *label;
const char *inputs_fmt; /* %s = cwd */
} cases[] = {
/* Real bootstrap-style links: every selfhost main.o paired
* with libwwrt.a. Exercises both the .o code path and the
* archive two-pass loader (rt_alloc / rt_syscall / rt_abort
* are pulled from libwwrt.a as undefs are encountered). */
{ "wwdump+libwwrt",
"%s/selfhost/cmd/wwdump/main.o %s/out/lib/libwwrt.a" },
{ "w6a+libwwrt",
"%s/selfhost/cmd/w6a/main.o %s/out/lib/libwwrt.a" },
{ "w6l+libwwrt",
"%s/selfhost/cmd/w6l/main.o %s/out/lib/libwwrt.a" },
{ NULL, NULL },
};
int fail = 0;
int n = 0;
for (int i = 0; cases[i].label; i++) {
char inputs[2048];
snprintf(inputs, sizeof inputs, cases[i].inputs_fmt, cwd, cwd);
if (diff_one(bin, cases[i].label, inputs) != 0) fail++;
n++;
}
if (fail) {
fprintf(stderr, "w6l_ww: %d/%d diff(s) failed\n", fail, n);
return 1;
}
printf("w6l_ww: byte-identical to C w6l on %d corpus links "
"(.o + libwwrt.a archive resolution)\n", n);
return 0;
}

177
test/wcc/993_ww_ww.c Normal file
View File

@@ -0,0 +1,177 @@
/*
* 993_ww_ww — phase-10 marker for the ww-side driver port.
*
* Diffs the C-built `ww` against the ww-built `ww_ww`. Both drivers
* orchestrate the same w6c/w6a/w6l, so the resulting binaries must be
* byte-identical for every program in the corpus. The corpus mixes:
* - a tiny single-file program (no -I flags)
* - a multi-import build (selfhost/cmd/wwdump/main.ww), exercising
* `use` resolution + the -I search path + the libwwrt.a link.
*
* Each build runs from a per-driver scratch directory so the
* intermediate .combined.ww/.s/.o files don't collide.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
/* Build `src` via the named driver, expecting output binary `out` in
* the build directory. Returns 0 on success. */
static int
build_via(const char *bin, const char *driver, const char *src,
const char *workdir, const char *includes_a, const char *includes_b)
{
char cmd[4096];
if (includes_b && includes_b[0]) {
snprintf(cmd, sizeof cmd,
"cd %s && %s/%s build -I %s -I %s %s 2>/dev/null",
workdir, bin, driver, includes_a, includes_b, src);
} else if (includes_a && includes_a[0]) {
snprintf(cmd, sizeof cmd,
"cd %s && %s/%s build -I %s %s 2>/dev/null",
workdir, bin, driver, includes_a, src);
} else {
snprintf(cmd, sizeof cmd,
"cd %s && %s/%s build %s 2>/dev/null",
workdir, bin, driver, src);
}
return runwait(cmd);
}
static int
diff_one(const char *bin, const char *cwd, const char *label,
const char *src, const char *out_basename,
const char *inc_a, const char *inc_b)
{
char dc[64], dw[64];
snprintf(dc, sizeof dc, "/tmp/ww_d_%d_c", getpid());
snprintf(dw, sizeof dw, "/tmp/ww_d_%d_w", getpid());
char cmd[256];
snprintf(cmd, sizeof cmd, "rm -rf %s %s && mkdir -p %s %s", dc, dw, dc, dw);
if (runwait(cmd) != 0) return -1;
if (build_via(bin, "ww", src, dc, inc_a, inc_b) != 0) {
fprintf(stderr, "ww_ww FAIL: C ww errored on %s\n", label);
return -1;
}
if (build_via(bin, "ww_ww", src, dw, inc_a, inc_b) != 0) {
fprintf(stderr, "ww_ww FAIL: ww ww errored on %s\n", label);
return -1;
}
char co[1024], wo[1024];
snprintf(co, sizeof co, "%s/%s", dc, out_basename);
snprintf(wo, sizeof wo, "%s/%s", dw, out_basename);
int rc = 0;
if (slurp_eq(co, wo) != 0) {
fprintf(stderr, "ww_ww FAIL: %s — driver outputs differ\n", label);
rc = -1;
}
snprintf(cmd, sizeof cmd, "rm -rf %s %s", dc, dw);
runwait(cmd);
(void)cwd;
return rc;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
/* Stage a tiny program in a place both drivers can reach. */
{
FILE *f = fopen("/tmp/ww_d_hello.ww", "w");
if (!f) return 1;
fputs("use os;\n\n"
"export fn main() i32 = {\n"
"\tos.write(1, \"hi\\n\".ptr, 3u64);\n"
"\treturn 0;\n"
"};\n", f);
fclose(f);
}
struct {
const char *label;
const char *src; /* may be relative to cwd */
const char *out; /* basename of expected output */
const char *inc_a;
const char *inc_b;
} cases[] = {
{ "hello", "/tmp/ww_d_hello.ww", "ww_d_hello", "", "" },
{ "wwdump", NULL, "main", NULL, NULL }, /* filled in below */
{ NULL, NULL, NULL, NULL, NULL },
};
/* wwdump case: absolute paths so the driver finds the imports
* regardless of the per-driver workdir. */
static char wwdump_src[2048], wwdump_inc_a[2048], wwdump_inc_b[2048];
snprintf(wwdump_src, sizeof wwdump_src, "%s/selfhost/cmd/wwdump/main.ww", cwd);
snprintf(wwdump_inc_a, sizeof wwdump_inc_a, "%s/selfhost/cmd/wcc", cwd);
wwdump_inc_b[0] = '\0';
cases[1].src = wwdump_src;
cases[1].inc_a = wwdump_inc_a;
cases[1].inc_b = wwdump_inc_b;
int fail = 0;
int n = 0;
for (int i = 0; cases[i].label; i++) {
if (diff_one(bin, cwd, cases[i].label, cases[i].src,
cases[i].out, cases[i].inc_a, cases[i].inc_b) != 0)
fail++;
n++;
}
unlink("/tmp/ww_d_hello.ww");
if (fail) {
fprintf(stderr, "ww_ww: %d/%d diff(s) failed\n", fail, n);
return 1;
}
printf("ww_ww: byte-identical to C ww on %d corpus builds "
"(single-file + selfhost/wwdump multi-import)\n", n);
return 0;
}

184
test/wcc/994_w6c_ww.c Normal file
View File

@@ -0,0 +1,184 @@
/*
* 994_w6c_ww — phase-10 marker for the ww-side w6c port.
*
* w6c_ww is a thin packaging of selfhost/cmd/wcc/cgen.ww: it slurps a
* .ww file, runs lex+parse+cgen, and writes Plan 9 amd64 asm to the
* file given by -o. The same cgen is reached through `wwdump_ww -c`,
* so w6c_ww must produce byte-identical output to wwdump_ww -c on
* every program — this test pins that.
*
* (We do not diff against C-side `w6c` here because the C compiler has
* features the ww cgen has not yet ported — float compare, fn-address
* @symbol, indirect call. Test 990 probe 5 covers the C-vs-ww diff on
* the subset that the ww cgen handles today.)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp(const char *path, char **outbuf, size_t *outlen)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
char *b = malloc((size_t)n + 1);
if (!b) { fclose(f); return -1; }
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = '\0';
fclose(f);
*outbuf = b;
*outlen = (size_t)n;
return 0;
}
static int
diff_one(const char *bin, const char *label, const char *src)
{
char ws[64], cs[64], cmd[2048];
snprintf(ws, sizeof ws, "/tmp/wwc6_%d_w.s", getpid());
snprintf(cs, sizeof cs, "/tmp/wwc6_%d_c.s", getpid());
snprintf(cmd, sizeof cmd, "%s/wwdump_ww -c %s > %s 2>/dev/null",
bin, src, ws);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6c_ww FAIL: wwdump_ww -c errored on %s\n", label);
unlink(ws);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6c_ww FAIL: w6c_ww errored on %s\n", label);
unlink(ws); unlink(cs);
return -1;
}
char *bw = NULL, *bc = NULL;
size_t nw = 0, nc = 0;
int rc = 0;
if (slurp(ws, &bw, &nw) < 0 || slurp(cs, &bc, &nc) < 0) {
fprintf(stderr, "w6c_ww FAIL: cannot read .s for %s\n", label);
rc = -1;
} else if (nw != nc || memcmp(bw, bc, nw) != 0) {
fprintf(stderr, "w6c_ww FAIL: %s — wwdump_ww %zu vs w6c_ww %zu bytes\n",
label, nw, nc);
rc = -1;
}
free(bw); free(bc);
unlink(ws); unlink(cs);
return rc;
}
static int
write_file(const char *path, const char *content)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(content, f);
fclose(f);
return 0;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
/* In-source corpus: programs the ww-side cgen handles today.
* Mirrors probe 5 in 990_selfhost without the parts that drift
* against C w6c. */
struct { const char *label; const char *src; } progs[] = {
{ "ret42",
"fn main() i32 = { return 42; };" },
{ "add",
"fn add(a: i32, b: i32) i32 = { return a + b; };\n"
"fn main() i32 = { return add(7, 35); };" },
{ "sum_for",
"fn sum(n: i32) i32 = {\n"
" let s: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < n) { s += i; i += 1; };\n"
" return s;\n"
"};\n"
"fn main() i32 = { return sum(10); };" },
{ "if_else",
"fn check(x: i32) i32 = {\n"
" if (x > 0) { return 1; };\n"
" if (x < 0) { return 100; };\n"
" return 0;\n"
"};\n"
"fn main() i32 = { return check(7); };" },
{ "recursion",
"fn fact(n: i32) i32 = {\n"
" if (n <= 1) { return 1; };\n"
" return n * fact(n - 1);\n"
"};\n"
"fn main() i32 = { return fact(5); };" },
{ NULL, NULL },
};
/* Source-tree corpus: the same .combined.ww files the bootstrap
* fixed point chews on. Confirms w6c_ww handles realistic inputs,
* not just hand-tailored ones. */
const char *combined_rel[] = {
"selfhost/cmd/wwdump/main.combined.ww",
"selfhost/cmd/w6a/main.combined.ww",
"selfhost/cmd/w6l/main.combined.ww",
"selfhost/cmd/ww/main.combined.ww",
NULL,
};
int fail = 0, n = 0;
for (int i = 0; progs[i].label; i++) {
char src[64];
snprintf(src, sizeof src, "/tmp/wwc6_%d_%d.ww", getpid(), i);
if (write_file(src, progs[i].src) != 0) { fail++; n++; continue; }
if (diff_one(bin, progs[i].label, src) != 0) fail++;
unlink(src);
n++;
}
for (int i = 0; combined_rel[i]; i++) {
char p[2048];
snprintf(p, sizeof p, "%s/%s", cwd, combined_rel[i]);
if (diff_one(bin, combined_rel[i], p) != 0) fail++;
n++;
}
if (fail) {
fprintf(stderr, "w6c_ww: %d/%d diff(s) failed\n", fail, n);
return 1;
}
printf("w6c_ww: byte-identical to wwdump_ww -c on %d corpus inputs "
"(in-source + selfhost combined.ww)\n", n);
return 0;
}

146
test/wcc/995_self_rebuild.c Normal file
View File

@@ -0,0 +1,146 @@
/*
* 995_self_rebuild — the wwstage rebuilds itself.
*
* Drives ww_ww (the ww-side driver, which shells to w6c_ww/w6a_ww/w6l_ww)
* over each wwstage tool's source and diffs the result byte-for-byte
* against the cstage-built binary in $BIN. A green run means the
* toolchain can recompile itself without touching cc, modulo the
* cold-start binary — which is the v1.0 lock from PLAN.md.
*
* This is stricter than `make bootstrap`: that loop only proves the
* wwdump cgen self-stabilises; this proves every wwstage tool round-
* trips through the wwstage pipeline.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) {
if (fa) fclose(fa);
if (fb) fclose(fb);
return -1;
}
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
/* Each tool builds via `ww_ww build -I <local> -I selfhost/cmd/wcc src`.
* Some tools have a local module dir (w6a, w6l with sibling .ww files);
* wwc-only tools (w6c, ww, wwdump) just need the wwc -I. inc_local is
* "" for those.
*/
static int
rebuild_one(const char *bin, const char *cwd, const char *tool,
const char *src_rel, const char *inc_local)
{
char workdir[64];
snprintf(workdir, sizeof workdir, "/tmp/wwsr_%d_%s", getpid(), tool);
char cmd[4096];
snprintf(cmd, sizeof cmd, "rm -rf %s && mkdir -p %s", workdir, workdir);
if (runwait(cmd) != 0) return -1;
if (inc_local && inc_local[0]) {
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww_ww build -I %s/%s -I %s/selfhost/cmd/wcc "
"%s/%s >/dev/null 2>&1",
workdir, bin, cwd, inc_local, cwd, cwd, src_rel);
} else {
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww_ww build -I %s/selfhost/cmd/wcc "
"%s/%s >/dev/null 2>&1",
workdir, bin, cwd, cwd, src_rel);
}
if (runwait(cmd) != 0) {
fprintf(stderr, "self-rebuild FAIL: ww_ww build errored on %s\n", tool);
return -1;
}
char rebuilt[256], canonical[256];
snprintf(rebuilt, sizeof rebuilt, "%s/main", workdir);
snprintf(canonical, sizeof canonical, "%s/%s_ww", bin, tool);
int rc = slurp_eq(rebuilt, canonical);
if (rc != 0) {
fprintf(stderr, "self-rebuild FAIL: %s rebuilt != cstage %s\n",
tool, canonical);
}
/* Leave the driver's intermediates (.s/.o/.combined.ww) next to the
* source — 991/992/994 read those fixtures, and `make wwstage` had
* already produced byte-identical copies anyway. */
snprintf(cmd, sizeof cmd, "rm -rf %s", workdir);
runwait(cmd);
return rc;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
struct {
const char *tool;
const char *src;
const char *inc_local;
} tools[] = {
{ "w6c", "selfhost/cmd/w6c/main.ww", "" },
{ "w6a", "selfhost/cmd/w6a/main.ww", "selfhost/cmd/w6a" },
{ "w6l", "selfhost/cmd/w6l/main.ww", "selfhost/cmd/w6l" },
{ "ww", "selfhost/cmd/ww/main.ww", "" },
{ "wwdump", "selfhost/cmd/wwdump/main.ww", "" },
{ NULL, NULL, NULL },
};
int fail = 0, n = 0;
for (int i = 0; tools[i].tool; i++) {
if (rebuild_one(bin, cwd, tools[i].tool, tools[i].src,
tools[i].inc_local) != 0)
fail++;
n++;
}
if (fail) {
fprintf(stderr, "self-rebuild: %d/%d tool(s) diverged\n", fail, n);
return 1;
}
printf("self-rebuild: %d wwstage tool(s) round-trip byte-identical "
"through ww_ww + w6c_ww + w6a_ww + w6l_ww\n", n);
return 0;
}

122
test/wcc/996_dyn_ww.c Normal file
View File

@@ -0,0 +1,122 @@
/*
* 996_dyn_ww — phase-8 marker for ET_DYN linking on the ww side.
*
* Drives w6l_ww with -L/-l flags over examples/snake/snake.o and diffs
* the result against the C-built w6l on the same inputs. A green run
* means the ww-side linker emits PT_INTERP/PT_DYNAMIC binaries byte-
* for-byte identical to the C linker — i.e. the dynamic linking story
* is fully ported and snake no longer depends on Cstage.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static const char *
absbin(void)
{
const char *b = getenv("BIN");
if (!b) b = "out/bin";
if (b[0] == '/') return b;
static char buf[2048];
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
return buf;
}
static int
slurp(const char *path, char **outbuf, size_t *outlen)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
char *b = malloc((size_t)n + 1);
if (!b) { fclose(f); return -1; }
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = '\0';
fclose(f);
*outbuf = b;
*outlen = (size_t)n;
return 0;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
/* Ensure snake.o exists. The example's Makefile produces it via
* the C-side `ww build`. Re-run that so the test is self-contained. */
char cmd[4096];
snprintf(cmd, sizeof cmd,
"cd %s/examples/snake && %s/ww build snake.ww -L /usr/lib "
"-l ncurses -l c >/dev/null 2>&1",
cwd, bin);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6l_ww-dyn FAIL: cannot build snake.o via C driver\n");
return 1;
}
char co[64], wo[64];
snprintf(co, sizeof co, "/tmp/wwld_%d_c", getpid());
snprintf(wo, sizeof wo, "/tmp/wwld_%d_w", getpid());
/* C-side w6l with the dynamic flags. */
snprintf(cmd, sizeof cmd,
"%s/w6l -o %s %s/examples/snake/snake.o -L /usr/lib "
"-l ncurses -l c %s/out/lib/libwwrt.a 2>/dev/null",
bin, co, cwd, cwd);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6l_ww-dyn FAIL: C w6l errored\n");
unlink(co);
return 1;
}
/* ww-side w6l with the same flags. */
snprintf(cmd, sizeof cmd,
"%s/w6l_ww -o %s %s/examples/snake/snake.o -L /usr/lib "
"-l ncurses -l c %s/out/lib/libwwrt.a 2>/dev/null",
bin, wo, cwd, cwd);
if (runwait(cmd) != 0) {
fprintf(stderr, "w6l_ww-dyn FAIL: ww w6l errored\n");
unlink(co); unlink(wo);
return 1;
}
char *bc = NULL, *bw = NULL;
size_t nc = 0, nw = 0;
int rc = 0;
if (slurp(co, &bc, &nc) < 0 || slurp(wo, &bw, &nw) < 0) {
fprintf(stderr, "w6l_ww-dyn FAIL: cannot read outputs\n");
rc = 1;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "w6l_ww-dyn FAIL: C %zu vs ww %zu bytes\n",
nc, nw);
rc = 1;
} else {
printf("w6l_ww-dyn: byte-identical to C w6l on snake "
"(PT_INTERP + PT_DYNAMIC + .rela.plt + .gnu.version_r, "
"%zu bytes)\n", nc);
}
free(bc); free(bw);
unlink(co); unlink(wo);
return rc;
}