ww: import toolchain — C bootstrap + ww-side self-host (phases 0-10)

C bootstrap (phases 0-9):
  cmd/wwc, cmd/6c, cmd/6a, cmd/6l, cmd/ww, rt, lib/*.

ww-side self-host (phase 10):
  selfhost/cmd/wwc — ww-cgen frontend; bootstrap fixed point.
  selfhost/cmd/6a  — assembler; byte-identical to C 6a (test 991).
  selfhost/cmd/6l  — linker w/ archive (.a) support; byte-identical
                     to C 6l (test 992).
  selfhost/cmd/ww  — driver (build/run/version); byte-identical to
                     C ww (test 993).

make test: 15/15. make bootstrap: ww2.s == ww3.s, ww2.o == ww3.o,
ww2 == ww3 byte-identical, with the full ww-tooled chain.
This commit is contained in:
2026-05-11 02:17:47 +09:00
parent 4c8fc59ca1
commit 1657bdeda3
106 changed files with 35654 additions and 15 deletions

66
test/run Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/sh
# test/run — driver for `make test`. Plan 9 rc-flavoured but plain sh.
#
# Walks the test/ tree:
# test/wwc/<NNN>_<name>.c → out/bin/test_<name> binary, run it.
# test/lang/<phase>/*.ww → compile/run via $WW, compare stdout/exit
# with header comments (// expected: ...).
# Exit non-zero on first failure.
set -e
BIN=${BIN:-out/bin}
WW=${WW:-$BIN/ww}
fail=0
ran=0
# ---- C-side unit tests --------------------------------------------------
for t in test/wwc/*.c; do
[ -f "$t" ] || continue
name=${t##*/}
name=${name%.c}
# strip leading <NNN>_
short=${name#[0-9][0-9][0-9]_}
bin=$BIN/test_$short
if [ ! -x "$bin" ]; then
echo "SKIP $name (no binary $bin)"
continue
fi
if "$bin" >"$BIN/.$short.out" 2>"$BIN/.$short.err"; then
echo "ok $name"
else
rc=$?
echo "FAIL $name (rc=$rc)"
echo "--- stdout ---"
cat "$BIN/.$short.out"
echo "--- stderr ---"
cat "$BIN/.$short.err"
fail=$((fail + 1))
fi
ran=$((ran + 1))
done
# ---- ww source-level tests ----------------------------------------------
# Each test/lang/<phase>/*.ww has a header:
# // expected-exit: 0
# // expected-stdout: hello world
# These run with `ww run`; phases that don't compile yet skip entries
# that begin with `// phase: <N>` if N > current.
if [ -d test/lang ]; then
for f in test/lang/*/*.ww; do
[ -f "$f" ] || continue
# Skip until ww run exists; placeholder for phase 7+
echo "skip $f (ww run not online yet)"
done
fi
if [ $ran -eq 0 ]; then
echo "no tests were run"
exit 1
fi
if [ $fail -gt 0 ]; then
echo "$fail test(s) failed"
exit 1
fi
echo "all $ran tests passed"

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

@@ -0,0 +1,87 @@
/*
* 000_smoke — Phase 0 smoke test.
*
* Asserts:
* - libwwc 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/wwc/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/wwc/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/wwc/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/wwc/400_6c.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/6c %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: 6c 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 6c tests failed\n", fail, n); return 1; }
printf("6c: %d/%d ok\n", n, n);
return 0;
}

62
test/wwc/500_6a.c Normal file
View File

@@ -0,0 +1,62 @@
/*
* 500_asm — assembler smoke. Drive 6c on a tiny program, feed the
* output to 6a, 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/6c -o %s %s", bin, asmf, src);
if (run(cmd) != 0) { fprintf(stderr, "6c fail: %s\n", programs[i]); fail++; continue; }
snprintf(cmd, sizeof cmd, "%s/6a -o %s %s", bin, obj, asmf);
if (run(cmd) != 0) { fprintf(stderr, "6a 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 6a tests failed\n", fail, n); return 1; }
printf("6a: %d/%d ok\n", n, n);
return 0;
}

67
test/wwc/600_6l.c Normal file
View File

@@ -0,0 +1,67 @@
/*
* 600_6l — linker smoke. Drive 6c → 6a → 6l 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/6c -o %s %s", bin, asmf, src);
if (system(cmd) != 0) { fprintf(stderr, "6c failed\n"); return 1; }
snprintf(cmd, sizeof cmd, "%s/6a -o %s %s", bin, obj, asmf);
if (system(cmd) != 0) { fprintf(stderr, "6a failed\n"); return 1; }
snprintf(cmd, sizeof cmd, "%s/6l -o %s %s", bin, exe, obj);
if (system(cmd) != 0) { fprintf(stderr, "6l 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("6l: ok\n");
return 0;
}

96
test/wwc/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 6l 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/6c -o lib_good.s lib_good.ww && "
"%s/6c -o lib_bad.s lib_bad.ww && "
"%s/6a -o lib_good.o lib_good.s && %s/6a -o lib_bad.o lib_bad.s && "
"ar rcs libfoo.a lib_good.o lib_bad.o && "
"%s/6c -o m.s m.ww && %s/6a -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/6l -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;
}

863
test/wwc/700_e2e.c Normal file
View File

@@ -0,0 +1,863 @@
/*
* 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 },
/* 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/wwc/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/6c %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/6c %s > /dev/null 2>&1", bin, path);
int rc = system(cmd);
if (rc != 0) {
fprintf(stderr, "ffi: 6c failed on libc.ww (rc=%d)\n", rc);
fail++;
}
}
if (fail) return 1;
puts("ffi: ok");
return 0;
}

65
test/wwc/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/6c %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/wwc/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 `6c`.
*
* 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 `6c`. */
static int
probe_codegen(const char *bin)
{
static const char *files[] = {
"selfhost/cmd/wwc/mem.ww",
"selfhost/cmd/wwc/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/6c %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/wwc/mem.ww",
"selfhost/cmd/wwc/err.ww",
"selfhost/cmd/wwc/lex.ww",
"selfhost/cmd/wwc/tok.ww",
"selfhost/cmd/wwc/ast.ww",
"selfhost/cmd/wwc/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/wwc/err.ww",
"selfhost/cmd/wwc/mem.ww",
"selfhost/cmd/wwc/lex.ww",
"selfhost/cmd/wwc/tok.ww",
"selfhost/cmd/wwc/ast.ww",
"selfhost/cmd/wwc/parse.ww",
"selfhost/cmd/wwc/typ.ww",
"selfhost/cmd/wwc/sym.ww",
"selfhost/cmd/wwc/check.ww",
"selfhost/cmd/wwc/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 6c 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 6l/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/6c %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/6a -o %s %s 2>/dev/null", bin, obj, wws);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-compile prog %d: 6a failed\n", i);
fail++; goto cleanup;
}
snprintf(cmd, sizeof cmd, "%s/6l -o %s %s %s 2>/dev/null",
bin, exe, obj, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-compile prog %d: 6l 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/wwc/err.ww",
"selfhost/cmd/wwc/mem.ww",
"selfhost/cmd/wwc/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/wwc/mem.ww",
"selfhost/cmd/wwc/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/6a -o %s %s 2>/dev/null", bin, ww2o, ww2s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: 6a ww2.s\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "%s/6l -o %s %s %s 2>/dev/null", bin, ww2e, ww2o, rt);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: 6l 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/6a -o %s %s 2>/dev/null", bin, ww3o, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: 6a ww3.s\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "%s/6l -o %s %s %s 2>/dev/null", bin, ww3e, ww3o, rt);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: 6l 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 6a/6l 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/6a -o %s %s 2>/dev/null", bin, obj_p, asm_p);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww1->ww2 FAIL: 6a 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/6l -o %s %s %s 2>/dev/null",
bin, exe_p, obj_p, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww1->ww2 FAIL: 6l 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 → 6a →
* 6l → 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/wwc %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/6a -o %s %s 2>/dev/null",
bin, wop, wsp);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: 6a %s\n", fix);
fail++;
goto cleanup;
}
char rt[1024];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
snprintf(cmd, sizeof cmd, "%s/6l -o %s %s %s 2>/dev/null",
bin, wexep, wop, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: 6l %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 6c 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/wwc/mem.ww",
"selfhost/cmd/wwc/err.ww",
"selfhost/cmd/wwc/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/6c %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/wwc/991_6a_ww.c Normal file
View File

@@ -0,0 +1,135 @@
/*
* 991_6a_ww — phase-10 marker for the ww-side 6a port.
*
* Diffs the C-built `6a` against the ww-built `6a_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/6a/6l main.s) - large, exercises
* every encoding path 6c emits
*
* Any divergence is a port bug in selfhost/cmd/6a/.
*/
#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/6a -o %s %s 2>/dev/null", bin, co, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "6a_ww FAIL: C 6a errored on %s\n", src);
unlink(co);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/6a_ww -o %s %s 2>/dev/null", bin, wo, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "6a_ww FAIL: ww 6a 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, "6a_ww FAIL: cannot read .o for %s\n", src);
rc = -1;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "6a_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 6c emits (the only way to hit
* the long tail of operand shapes). */
"selfhost/cmd/wwdump/main.s",
"selfhost/cmd/6l/main.s",
"selfhost/cmd/6a/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, "6a_ww: %d/%d diff(s) failed\n", fail, n);
return 1;
}
printf("6a_ww: byte-identical to C 6a on %d corpus files "
"(rt/*.s + selfhost generated main.s)\n", n);
return 0;
}

137
test/wwc/992_6l_ww.c Normal file
View File

@@ -0,0 +1,137 @@
/*
* 992_6l_ww — phase-10 marker for the ww-side 6l port.
*
* Diffs the C-built `6l` against the ww-built `6l_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/6l/.
*/
#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/6l -o %s %s 2>/dev/null", bin, co, inputs);
if (runwait(cmd) != 0) {
fprintf(stderr, "6l_ww FAIL: C 6l errored on %s\n", label);
unlink(co);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/6l_ww -o %s %s 2>/dev/null", bin, wo, inputs);
if (runwait(cmd) != 0) {
fprintf(stderr, "6l_ww FAIL: ww 6l 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, "6l_ww FAIL: cannot read output for %s\n", label);
rc = -1;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "6l_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" },
{ "6a+libwwrt",
"%s/selfhost/cmd/6a/main.o %s/out/lib/libwwrt.a" },
{ "6l+libwwrt",
"%s/selfhost/cmd/6l/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, "6l_ww: %d/%d diff(s) failed\n", fail, n);
return 1;
}
printf("6l_ww: byte-identical to C 6l on %d corpus links "
"(.o + libwwrt.a archive resolution)\n", n);
return 0;
}

177
test/wwc/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 6c/6a/6l, 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/wwc", 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;
}