test/wcc: retire 990_selfhost; its live assertions move to their owners
Every probe's assertion is owned by a current gate: the compile and link probes by make all and the bootstrap fixed point; build/run and cs/ww byte identity by the fixture corpus, test-data-byteid, and 989_lib_byteid; wwstage driver and toolchain parity by 993/995; checker-diagnostic parity by the corpus' both-stage //ww:error rows. The wwdump -t/-a dump-parity probes gated the frontend port's convergence, which the compiler-output identity gates now own end to end; carrier ran green at retirement. What was still uniquely alive migrates: smoke.ww becomes corpus fixture selfhost_smoke (upgraded from a cstage-only build to both frontends, byte-identical, exit 42 on both toolchains; corpus pins move to 1,225/763/2,450 with the new identity hash in the same commit), and sym_link.ww's scope/sym behavior rows become in-language lib/ww/syntax/symtest.ww under LIBRARY_TESTS. uses.ww (parser-stub-era -a fixture) and the already-orphaned tagged_ptr_ret.ww/trypromote.ww retire with the probe corpus. Bootstrap native gates drop to six; frontend numeric-sync comments now cite the rule-6 mirror instead of the retired diff probe.
This commit is contained in:
@@ -1,212 +0,0 @@
|
||||
// selfhost/test/smoke.ww — end-to-end smoke for the selfhost path.
|
||||
//
|
||||
// Exercises the patterns the real ww-side compiler port will use:
|
||||
// - bump arena allocator (mem.ww shape)
|
||||
// - error idiom (T | str)
|
||||
// - struct of fn pointers + ctx pointer (the io.stream-style
|
||||
// polymorphism we use instead of interfaces)
|
||||
// - byte-level scanning that mirrors the hot path inside lex.ww
|
||||
// - strconv round-trip via the real stdlib
|
||||
//
|
||||
// `main` returns 42 when every check passes, 1..N on failure
|
||||
// indicating which probe broke. The 990_selfhost test asserts 42.
|
||||
//
|
||||
// Note: only stack-local mutable state. Top-level `let` mutation
|
||||
// requires a writable .data segment in w6l, which is a separate
|
||||
// task; until then we exercise polymorphism via ctx pointers, which
|
||||
// is what the real port wants anyway.
|
||||
|
||||
package test;
|
||||
|
||||
import os;
|
||||
import strconv;
|
||||
import ascii;
|
||||
|
||||
// --- bump arena ---------------------------------------------------------
|
||||
|
||||
type arena = struct {
|
||||
buf: *u8,
|
||||
off: u64,
|
||||
cap: u64,
|
||||
};
|
||||
|
||||
// In-place init. Returning a 24-byte struct by value isn't yet
|
||||
// supported in w6c (SysV requires a hidden return-slot pointer for
|
||||
// structs >16 bytes), so we initialize through a pointer like the
|
||||
// real compiler does today.
|
||||
fn arena_init(a: *arena, buf: *u8, cap: u64) void = {
|
||||
a.buf = buf;
|
||||
a.off = 0u64;
|
||||
a.cap = cap;
|
||||
};
|
||||
|
||||
fn arena_alloc(a: *arena, n: u64) *u8 = {
|
||||
if (n > a.cap - a.off) { return nil; };
|
||||
let p: *u8 = a.buf + a.off;
|
||||
a.off += n;
|
||||
return p;
|
||||
};
|
||||
|
||||
// --- (i32 | str) error idiom -------------------------------------------
|
||||
|
||||
fn checked_div(num: i32, den: i32) (i32 | str) = {
|
||||
if (den == 0) { return "div by zero"; };
|
||||
return num / den;
|
||||
};
|
||||
|
||||
// --- struct-of-fn-pointer polymorphism ---------------------------------
|
||||
//
|
||||
// A trivial "writer" abstraction: a function pointer plus a context.
|
||||
// This mirrors how io.stream / Plan 9 Bio work. The ctx pointer lets
|
||||
// the implementation own its own state without a global.
|
||||
|
||||
type counter = struct {
|
||||
n: i32,
|
||||
};
|
||||
|
||||
type writer = struct {
|
||||
ctx: *void,
|
||||
emit: fn(ctx: *void, b: u8) void,
|
||||
};
|
||||
|
||||
fn count_emit(ctx: *void, b: u8) void = {
|
||||
let c: *counter = ctx: *counter;
|
||||
c.n += 1;
|
||||
};
|
||||
|
||||
// --- size/align/offset typed-builtin fixtures (#42) --------------------
|
||||
|
||||
type point = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
};
|
||||
|
||||
// Mixed-alignment struct: i8 lays at 0, then i64 needs to skip to
|
||||
// offset 8 (the i64's natural align). Probe asserts both ends.
|
||||
type mixalign = struct {
|
||||
tag: i8,
|
||||
val: i64,
|
||||
};
|
||||
|
||||
// --- byte scanner like lex.ww's hot path -------------------------------
|
||||
|
||||
fn count_digits(s: str) i32 = {
|
||||
let i: i32 = 0;
|
||||
let n: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c >= 48u8) {
|
||||
if (c <= 57u8) { n += 1; };
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
// --- entry --------------------------------------------------------------
|
||||
|
||||
export fn main() i32 = {
|
||||
// Probe 1 — arena hands out distinct pointers, refuses oversize.
|
||||
let buf: [256]u8;
|
||||
let a: arena;
|
||||
arena_init(&a, buf.ptr, 256u64);
|
||||
let p1: *u8 = arena_alloc(&a, 32u64);
|
||||
let p2: *u8 = arena_alloc(&a, 32u64);
|
||||
if (p1 == nil) { return 1; };
|
||||
if (p2 == nil) { return 2; };
|
||||
if (p1 == p2) { return 3; };
|
||||
let p3: *u8 = arena_alloc(&a, 1024u64);
|
||||
if (p3 != nil) { return 4; };
|
||||
|
||||
// Probe 2 — error union both ways.
|
||||
let r_ok: (i32 | str) = checked_div(84, 2);
|
||||
let r_bad: (i32 | str) = checked_div(1, 0);
|
||||
let acc: i32 = 0;
|
||||
match (r_ok) {
|
||||
case let v: i32 => acc = v;
|
||||
case let e: str => return 5;
|
||||
};
|
||||
if (acc != 42) { return 6; };
|
||||
match (r_bad) {
|
||||
case let v: i32 => return 7;
|
||||
case let e: str => acc = e.len: i32;
|
||||
};
|
||||
if (acc != 11) { return 8; }; // len("div by zero") == 11
|
||||
|
||||
// Probe 3 — struct-of-fn-pointer dispatch via ctx pointer.
|
||||
let c: counter = counter { n = 0 };
|
||||
let w: writer = writer { ctx = (&c): *void, emit = count_emit };
|
||||
w.emit(w.ctx, 65u8);
|
||||
w.emit(w.ctx, 66u8);
|
||||
w.emit(w.ctx, 67u8);
|
||||
if (c.n != 3) { return 9; };
|
||||
|
||||
// Probe 4 — byte scan over a literal.
|
||||
let dn: i32 = count_digits("ww123abc");
|
||||
if (dn != 3) { return 10; };
|
||||
|
||||
// Probe 5 — strconv round-trip via the real stdlib.
|
||||
let s: str = strconv.i64tos(4242i64, strconv.base.DEC);
|
||||
if (s.len != 4) { return 11; };
|
||||
if (s.ptr[0] != 52u8) { return 12; }; // '4'
|
||||
if (s.ptr[3] != 50u8) { return 13; }; // '2'
|
||||
|
||||
// Probe 6 — ascii classifications (rune-taking, Hare-shaped).
|
||||
if (!ascii.isdigit(53)) { return 14; }; // '5'
|
||||
if (ascii.isdigit(65)) { return 15; }; // 'A' is not a digit
|
||||
if (!ascii.isalpha(122)) { return 16; }; // 'z'
|
||||
if (!ascii.isxdigit(70)) { return 17; }; // 'F'
|
||||
if (ascii.isxdigit(71)) { return 18; }; // 'G' is not hex
|
||||
if (ascii.tolower(65) != 97) { return 19; }; // 'A' -> 'a'
|
||||
if (ascii.toupper(122) != 90) { return 20; }; // 'z' -> 'Z'
|
||||
|
||||
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
||||
// always exists on Linux, no write side, and is non-empty.
|
||||
let path: str = "/proc/self/cmdline";
|
||||
// Use raw os.open here (returns i32 with -errno) for the same
|
||||
// reason as os.read below: probe 6 in 990_selfhost compiles
|
||||
// smoke.ww standalone (no `use` expansion), so cross-module type
|
||||
// references like `os.oserror` and `os.flag` don't resolve at
|
||||
// that step. RDONLY is 0; passing the literal keeps the call
|
||||
// site standalone-compilable to byte-identical asm on both
|
||||
// compilers.
|
||||
let fd: i32 = os.open(path, 0, 0i32);
|
||||
if (fd < 0) { return 21; };
|
||||
let rbuf: [128]u8;
|
||||
// Use raw os.read here (single syscall, plain i64) instead of
|
||||
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
||||
// standalone without `use os;` expansion, so cross-module type
|
||||
// references like `os.oserror` can't be resolved.
|
||||
let n: i64 = os.read(fd, rbuf.ptr, 128u64);
|
||||
os.close(fd);
|
||||
if (n <= 0i64) { return 22; };
|
||||
|
||||
// Probe 8 — size(T) / align(T) / offset(e.f) typed-builtin folds
|
||||
// (#42). Each call folds to an N_INTLIT at check time; cgen
|
||||
// materialises the literal as a plain `MOVQ $N, AX`. Mirrors
|
||||
// cstage cmd/wcc/check.c:907-960 byte-for-byte on this corpus.
|
||||
if (size(str) != 24) { return 23; }; // str IS []u8: {ptr,len,cap} 24B (#1/Phase 3)
|
||||
if (size(i64) != 8) { return 24; };
|
||||
if (size(i32) != 4) { return 25; };
|
||||
if (align(i64) != 8) { return 26; };
|
||||
if (align(i32) != 4) { return 27; };
|
||||
// Initialize struct locals explicitly so the cgen path doesn't
|
||||
// drift from cstage on bare `let X: T;` zero-init (pre-existing
|
||||
// wwstage divergence outside #42).
|
||||
let pt: point = point { x = 0, y = 0 };
|
||||
if (offset(pt.x) != 0) { return 28; };
|
||||
if (offset(pt.y) != 4) { return 29; };
|
||||
let mx: mixalign = mixalign { tag = 0i8, val = 0i64 };
|
||||
if (offset(mx.tag) != 0) { return 30; };
|
||||
if (offset(mx.val) != 8) { return 31; }; // align-padded to 8
|
||||
// Width breadth: smallest prim, ptr, slice, struct (8B + padded),
|
||||
// covering astsize's TPTR/TSLICE/TNAME-resolve-to-struct arms.
|
||||
if (size(i8) != 1) { return 32; };
|
||||
if (align(i8) != 1) { return 33; };
|
||||
if (size(*i32) != 8) { return 34; };
|
||||
if (size([]i32) != 24) { return 35; };
|
||||
if (size(point) != 8) { return 36; };
|
||||
if (size(mixalign) != 16) { return 37; };
|
||||
|
||||
return 42;
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
// selfhost/test/sym_link.ww — link-and-run probe for the ww-cgen
|
||||
// against the sym/typ/ast dep stack. Exercises hashtable scope
|
||||
// (sym), and pulls in typ/ast as type carriers.
|
||||
// Returns 42 on success; smaller values name the probe that broke.
|
||||
|
||||
package test;
|
||||
|
||||
import syntax;
|
||||
|
||||
export fn main() i32 = {
|
||||
let s: *scope = newscope(nil);
|
||||
if (s == nil) { return 2; };
|
||||
|
||||
let n1: str = "foo";
|
||||
let r1: *sym = scopedefine(s, n1, skind.SK_VAR, nil, nil);
|
||||
if (r1 == nil) { return 3; };
|
||||
|
||||
let n2: str = "bar";
|
||||
let r2: *sym = scopedefine(s, n2, skind.SK_TYPE, nil, nil);
|
||||
if (r2 == nil) { return 4; };
|
||||
|
||||
// Duplicate define in same scope must fail.
|
||||
let r3: *sym = scopedefine(s, n1, skind.SK_VAR, nil, nil);
|
||||
if (r3 != nil) { return 5; };
|
||||
|
||||
let l1: *sym = scopelookup(s, n1);
|
||||
if (l1 == nil) { return 6; };
|
||||
if (l1.skind != skind.SK_VAR) { return 7; };
|
||||
|
||||
let l2: *sym = scopelookup(s, n2);
|
||||
if (l2 == nil) { return 8; };
|
||||
if (l2.skind != skind.SK_TYPE) { return 9; };
|
||||
|
||||
// Not-found lookup returns nil.
|
||||
let n3: str = "baz";
|
||||
let l3: *sym = scopelookup(s, n3);
|
||||
if (l3 != nil) { return 10; };
|
||||
|
||||
return 42;
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
// selfhost/test/tagged_ptr_ret.ww — smoke for the (*T | nomem) return ABI.
|
||||
//
|
||||
// Task #25 (ww-strings-redesign): cstage used to fold `(*T | !void)`-shaped
|
||||
// returns into the nullable-pointer-in-AX encoding (richer optimization),
|
||||
// while wwstage emitted the documented general tagged-return ABI
|
||||
// (AX=tag, DX=word0). Per CLAUDE.md rule 10 the richer side aligns DOWN —
|
||||
// cstage now restricts the nullable fold to literal `void` variants, so
|
||||
// `(*T | nomem)` takes the general path on both stages and the 993/995
|
||||
// byte-identity tests stay green.
|
||||
//
|
||||
// Task #29: `nomem` is now predeclared in the compiler universe scope, so
|
||||
// neither `import errors;` nor a local `type nomem = !void;` is needed.
|
||||
// Two match arms cover both runtime outcomes — success unwrap (tag=0,
|
||||
// ptr payload in DX) and error propagation (tag=1) — exercising the
|
||||
// same AX/DX ABI both stages must agree on.
|
||||
//
|
||||
// Task #30: the `alloc` builtin itself now returns `(*T | nomem)`. The
|
||||
// allocbox arm below propagates the builtin's tagged return through
|
||||
// the enclosing fn via `?` against a same-shape `(*point | nomem)`
|
||||
// — matching the team-lead spec's "exercise `alloc(T)?` against a
|
||||
// real function returning `(T | nomem)`".
|
||||
|
||||
package main;
|
||||
|
||||
import fmt;
|
||||
import os;
|
||||
|
||||
type point = struct { x: i32, y: i32 };
|
||||
|
||||
fn alloc1(fail: i64) (*u8 | nomem) = {
|
||||
if (fail != 0i64) { let e: nomem; return e; };
|
||||
let buf: [1]u8;
|
||||
return buf.ptr;
|
||||
};
|
||||
|
||||
fn caller(fail: i64) (*u8 | nomem) = {
|
||||
let p: *u8 = alloc1(fail)?;
|
||||
return p;
|
||||
};
|
||||
|
||||
fn allocbox() (*point | nomem) = {
|
||||
let p: *point = alloc(point { x = 3, y = 4 })?;
|
||||
return p;
|
||||
};
|
||||
|
||||
// Task #32: slice-form `let s: []T = alloc([], n)!;` shortcut. Both
|
||||
// stages must lower to `n*esz` bytes via rt_malloc, abort on null, and
|
||||
// build a {ptr, 0, n} header in the let slot. Pre-#32 wwstage fell
|
||||
// through to cgalloc, allocating 8B and dropping the slice header
|
||||
// entirely — silent miscompile. Cap-only would pass on a junk header
|
||||
// pointing to dead memory; write-then-read on s[0]/s[cap-1] proves
|
||||
// the ptr field is a real rt_malloc'd region (would SIGSEGV otherwise).
|
||||
// IMULQ esz path is currently unreachable from user code — check.c
|
||||
// pins the alloc shape to []u8 (cstage check.c:1052-1082) — so this
|
||||
// row only exercises esz=1; the cgen elemsizeofc resolution stays
|
||||
// defensive against a future check.c relaxation.
|
||||
fn sliceshort() i32 = {
|
||||
let s: []u8 = alloc([], 16)!;
|
||||
if (s.cap != 16) { return -1i32; };
|
||||
s[0] = 42u8;
|
||||
s[15] = 99u8;
|
||||
return (s[0]: i32) + (s[15]: i32);
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
let rc: i32 = 0;
|
||||
match (caller(0i64)) {
|
||||
case let p: *u8 => {
|
||||
fmt.println("ok");
|
||||
if (p == nil) { rc = 1; };
|
||||
};
|
||||
case nomem => {
|
||||
fmt.println("unexpected nomem on ok path");
|
||||
rc = 2;
|
||||
};
|
||||
};
|
||||
match (caller(1i64)) {
|
||||
case let p: *u8 => {
|
||||
fmt.println("unexpected ptr on err path");
|
||||
rc = 3;
|
||||
};
|
||||
case nomem => {
|
||||
fmt.println("nomem as expected");
|
||||
};
|
||||
};
|
||||
match (allocbox()) {
|
||||
case let p: *point => {
|
||||
fmt.println("allocbox ok");
|
||||
if (p.x * p.x + p.y * p.y != 25) { rc = 4; };
|
||||
};
|
||||
case nomem => {
|
||||
fmt.println("allocbox unexpected nomem");
|
||||
rc = 5;
|
||||
};
|
||||
};
|
||||
if (sliceshort() != 141i32) { rc = 6; }
|
||||
else { fmt.println("sliceshort ok"); };
|
||||
return rc;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
// selfhost/test/trypromote.ww — smoke for `?` propagation through a
|
||||
// `!void`-shaped alias.
|
||||
//
|
||||
// Task #2 (ww-strings-redesign): proves cgen's TRYPROP tag-remap works
|
||||
// for the exact pattern lib/errors + os.alloc are about to lean on. We
|
||||
// cannot smoke this against lib/ today because there are zero lib-side
|
||||
// `?` users on a `!void` alias yet. Mirrors the shlex.syntaxerr shape
|
||||
// (lib/shlex/shlex.ww:112) for the "nomem" stub.
|
||||
//
|
||||
// Not table-driven on purpose: same-shape `?` is a single cgen emit
|
||||
// pattern, so varying the operand exercises the same asm. The two
|
||||
// match arms below cover both runtime outcomes (success unwrap, error
|
||||
// propagation); asm-level regressions of task #18 are gated by the
|
||||
// byte-identity tests (994_w6c_ww, 995_self_rebuild).
|
||||
|
||||
package test;
|
||||
|
||||
import fmt;
|
||||
|
||||
// #29: `nomem` is predeclared in the universe scope — no local
|
||||
// `type nomem = !void;` (or `import errors;`) needed.
|
||||
|
||||
fn stub(fail: i64) (i64 | nomem) = {
|
||||
if (fail != 0i64) { let e: nomem; return e; };
|
||||
return 42i64;
|
||||
};
|
||||
|
||||
fn caller(fail: i64) (i64 | nomem) = {
|
||||
let v = stub(fail)?;
|
||||
return v + 1i64;
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
let rc: i32 = 0;
|
||||
match (caller(0i64)) {
|
||||
case let n: i64 => {
|
||||
fmt.println("ok ", n);
|
||||
if (n != 43i64) { rc = 1; };
|
||||
};
|
||||
case nomem => {
|
||||
fmt.println("unexpected nomem on ok path");
|
||||
rc = 2;
|
||||
};
|
||||
};
|
||||
match (caller(1i64)) {
|
||||
case let n: i64 => {
|
||||
fmt.println("unexpected ", n, " on err path");
|
||||
rc = 3;
|
||||
};
|
||||
case nomem => {
|
||||
fmt.println("nomem as expected");
|
||||
};
|
||||
};
|
||||
return rc;
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
// selfhost/test/uses.ww — AST-diff fixture. Grows as parse.ww does.
|
||||
// Currently exercises: `use IDENT;`, `def NAME: TYPE = LIT;`,
|
||||
// `type NAME = TYPE;` (alias + struct), top-level `let NAME: TYPE = LIT;`.
|
||||
//
|
||||
// Function declarations are still recovered past — the body parser
|
||||
// is the next major chunk. See lib/ww/parse.ww header.
|
||||
|
||||
package test;
|
||||
|
||||
import os;
|
||||
import fmt;
|
||||
|
||||
def MAX_LINE: i32 = 4096;
|
||||
def NAME: str = "ww";
|
||||
def READY: bool = true;
|
||||
|
||||
type byte = u8;
|
||||
type rune = i32;
|
||||
type pos = struct {
|
||||
file: str,
|
||||
line: i32,
|
||||
col: i32,
|
||||
};
|
||||
type buffer = [4096]u8;
|
||||
type bytes = []u8;
|
||||
type linkptr = *byte;
|
||||
|
||||
let nerrors: i32 = 0;
|
||||
let nwarnings: i32 = 0;
|
||||
let prog_name: str = "ww";
|
||||
Reference in New Issue
Block a user