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:
66
test/run
Executable file
66
test/run
Executable 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
87
test/wwc/000_smoke.c
Normal 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
142
test/wwc/100_lex.c
Normal 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
190
test/wwc/200_parse.c
Normal 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
126
test/wwc/300_check.c
Normal 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
90
test/wwc/400_6c.c
Normal 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
62
test/wwc/500_6a.c
Normal 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
67
test/wwc/600_6l.c
Normal 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
96
test/wwc/610_arch.c
Normal 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
863
test/wwc/700_e2e.c
Normal 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
80
test/wwc/800_ffi.c
Normal 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
65
test/wwc/900_stdlib.c
Normal 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
786
test/wwc/990_selfhost.c
Normal 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
135
test/wwc/991_6a_ww.c
Normal 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
137
test/wwc/992_6l_ww.c
Normal 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
177
test/wwc/993_ww_ww.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user