diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index fccf3f9f..a67be34d 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -53,6 +53,7 @@ lookup_builtin(const char *name) if (strcmp(name, "f64") == 0) return ty_f64; if (strcmp(name, "str") == 0) return ty_str; if (strcmp(name, "never") == 0) return ty_never; + if (strcmp(name, "nomem") == 0) return ty_nomem; /* #29 */ return NULL; } diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index 5d0e2063..a91cd033 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -16,6 +16,7 @@ Type *ty_int, *ty_uint, *ty_uintptr; Type *ty_f32, *ty_f64, *ty_str; Type *ty_err; Type *ty_never; +Type *ty_nomem; Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str; Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil; @@ -63,6 +64,16 @@ typesinit(Arena *a) ty_str = prim(a, TY_STR, "str", 16, 8); ty_err = prim(a, TY_ERR, "", 0, 1); ty_never = prim(a, TY_NEVER, "never", 0, 1); + /* #29: predeclared `type nomem = !void;`. NAMED so variant_match + * compares by pointer identity (singleton across all references); + * under=ty_void + iserror=1 triggers the `!T` error-tag machinery + * the same way a user-declared alias would. */ + ty_nomem = newtype(a, TY_NAMED); + ty_nomem->name = "nomem"; + ty_nomem->under = ty_void; + ty_nomem->size = ty_void->size; + ty_nomem->align = ty_void->align; + ty_nomem->iserror = 1; ty_untyped_int = prim(a, TY_UNTYPED_INT, "untyped_int", 0, 1); ty_untyped_float = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0, 1); diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index 99eb3fc2..41dee3e8 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -440,6 +440,7 @@ extern Type *ty_int, *ty_uint, *ty_uintptr; extern Type *ty_f32, *ty_f64, *ty_str; extern Type *ty_err; extern Type *ty_never; +extern Type *ty_nomem; /* task #29: predeclared `!void` alias */ extern Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str; extern Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 221db16d..f16600f7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6859,6 +6859,23 @@ fn seedprimitives(c: *checker) void = { scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil); scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil); scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil); + // #29: predeclare `type nomem = !void;` so user code needn't + // declare it locally. Synthesize an nkind.N_TYPEDECL whose lhs is + // nkind.N_TBANG{nkind.N_TNAME("void")} so varianterr and other + // iserror-aware paths treat `nomem` identically to a user-written + // alias. Mirrors cmd/wcc/check.c lookup_builtin returning + // ty_nomem (NAMED, under=ty_void, iserror=1). Note: cgen owns a + // separate alias chain — see collectaliases in cgen.ww for the + // companion seed. + let empty: str; + let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0); + tnvoid.str = "void"; + let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0); + bang.lhs = tnvoid; + let nomemdecl: *node = newnode(c.a, nkind.N_TYPEDECL, empty, 0, 0); + nomemdecl.str = "nomem"; + nomemdecl.lhs = bang; + scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl); // `nil`, `true`, `false` are keywords — handled at the lex/parser // level, no symbol needed. // `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine @@ -19634,6 +19651,27 @@ type aliasent = struct { fn collectaliases(c: *cgen, file: *node) void = { c.aliases = nil; + // #29: seed `type nomem = !void;` here AS WELL AS in check.ww's + // seedprimitives. The two seeds aren't redundant: wwstage's check + // owns c.top (used by name resolution); cgen owns its own + // c.aliases chain (used by resolvetype / slotsize / TBANG checks). + // Without this seed, resolvetype("nomem") returns the raw N_TNAME + // — slotsize falls through to 8B without zero-init, diverging from + // cstage's `let e: nomem;` MOVQ $0 emit on the slot (rule 10). + // Inserted at the head so the user-decl loop below prepends; the + // same-module / any-match passes in aliaslookup then let a local + // `type nomem = !void;` shadow this fallback within its module. + let empty: str; + let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0); + tnvoid.str = "void"; + let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0); + bang.lhs = tnvoid; + let nomemal: *aliasent = amalloc(c.a, 64u64): *aliasent; + nomemal.aname = "nomem"; + nomemal.amod = empty; + nomemal.target = bang; + nomemal.aanext = nil; + c.aliases = nomemal; let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_TYPEDECL) { diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 1368f99f..72d71e9e 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -55,6 +55,27 @@ type aliasent = struct { fn collectaliases(c: *cgen, file: *node) void = { c.aliases = nil; + // #29: seed `type nomem = !void;` here AS WELL AS in check.ww's + // seedprimitives. The two seeds aren't redundant: wwstage's check + // owns c.top (used by name resolution); cgen owns its own + // c.aliases chain (used by resolvetype / slotsize / TBANG checks). + // Without this seed, resolvetype("nomem") returns the raw N_TNAME + // — slotsize falls through to 8B without zero-init, diverging from + // cstage's `let e: nomem;` MOVQ $0 emit on the slot (rule 10). + // Inserted at the head so the user-decl loop below prepends; the + // same-module / any-match passes in aliaslookup then let a local + // `type nomem = !void;` shadow this fallback within its module. + let empty: str; + let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0); + tnvoid.str = "void"; + let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0); + bang.lhs = tnvoid; + let nomemal: *aliasent = amalloc(c.a, 64u64): *aliasent; + nomemal.aname = "nomem"; + nomemal.amod = empty; + nomemal.target = bang; + nomemal.aanext = nil; + c.aliases = nomemal; let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_TYPEDECL) { diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 9d298b99..454af26e 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -63,6 +63,23 @@ fn seedprimitives(c: *checker) void = { scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil); scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil); scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil); + // #29: predeclare `type nomem = !void;` so user code needn't + // declare it locally. Synthesize an nkind.N_TYPEDECL whose lhs is + // nkind.N_TBANG{nkind.N_TNAME("void")} so varianterr and other + // iserror-aware paths treat `nomem` identically to a user-written + // alias. Mirrors cmd/wcc/check.c lookup_builtin returning + // ty_nomem (NAMED, under=ty_void, iserror=1). Note: cgen owns a + // separate alias chain — see collectaliases in cgen.ww for the + // companion seed. + let empty: str; + let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0); + tnvoid.str = "void"; + let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0); + bang.lhs = tnvoid; + let nomemdecl: *node = newnode(c.a, nkind.N_TYPEDECL, empty, 0, 0); + nomemdecl.str = "nomem"; + nomemdecl.lhs = bang; + scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl); // `nil`, `true`, `false` are keywords — handled at the lex/parser // level, no symbol needed. // `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 84df1d3c..c7016c0d 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6859,6 +6859,23 @@ fn seedprimitives(c: *checker) void = { scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil); scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil); scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil); + // #29: predeclare `type nomem = !void;` so user code needn't + // declare it locally. Synthesize an nkind.N_TYPEDECL whose lhs is + // nkind.N_TBANG{nkind.N_TNAME("void")} so varianterr and other + // iserror-aware paths treat `nomem` identically to a user-written + // alias. Mirrors cmd/wcc/check.c lookup_builtin returning + // ty_nomem (NAMED, under=ty_void, iserror=1). Note: cgen owns a + // separate alias chain — see collectaliases in cgen.ww for the + // companion seed. + let empty: str; + let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0); + tnvoid.str = "void"; + let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0); + bang.lhs = tnvoid; + let nomemdecl: *node = newnode(c.a, nkind.N_TYPEDECL, empty, 0, 0); + nomemdecl.str = "nomem"; + nomemdecl.lhs = bang; + scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl); // `nil`, `true`, `false` are keywords — handled at the lex/parser // level, no symbol needed. // `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine @@ -19634,6 +19651,27 @@ type aliasent = struct { fn collectaliases(c: *cgen, file: *node) void = { c.aliases = nil; + // #29: seed `type nomem = !void;` here AS WELL AS in check.ww's + // seedprimitives. The two seeds aren't redundant: wwstage's check + // owns c.top (used by name resolution); cgen owns its own + // c.aliases chain (used by resolvetype / slotsize / TBANG checks). + // Without this seed, resolvetype("nomem") returns the raw N_TNAME + // — slotsize falls through to 8B without zero-init, diverging from + // cstage's `let e: nomem;` MOVQ $0 emit on the slot (rule 10). + // Inserted at the head so the user-decl loop below prepends; the + // same-module / any-match passes in aliaslookup then let a local + // `type nomem = !void;` shadow this fallback within its module. + let empty: str; + let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0); + tnvoid.str = "void"; + let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0); + bang.lhs = tnvoid; + let nomemal: *aliasent = amalloc(c.a, 64u64): *aliasent; + nomemal.aname = "nomem"; + nomemal.amod = empty; + nomemal.target = bang; + nomemal.aanext = nil; + c.aliases = nomemal; let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_TYPEDECL) { diff --git a/selfhost/test/tagged_ptr_ret.ww b/selfhost/test/tagged_ptr_ret.ww index 5131b1ef..f09b49b2 100644 --- a/selfhost/test/tagged_ptr_ret.ww +++ b/selfhost/test/tagged_ptr_ret.ww @@ -5,22 +5,19 @@ // 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)` (`type nomem = !void;`) takes the general path on both -// stages and the 993/995 byte-identity tests stay green once #17 lands a -// (*T | nomem) signature in lib/. +// `(*T | nomem)` takes the general path on both stages and the 993/995 +// byte-identity tests stay green. // -// nomem is declared locally because it is not yet predeclared in the -// universe scope (that move is #17). 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 #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. package test; import fmt; -type nomem = !void; - fn alloc1(fail: i64) (*u8 | nomem) = { if (fail != 0i64) { let e: nomem; return e; }; let buf: [1]u8; diff --git a/selfhost/test/trypromote.ww b/selfhost/test/trypromote.ww index 7f7f8fa7..06be42ad 100644 --- a/selfhost/test/trypromote.ww +++ b/selfhost/test/trypromote.ww @@ -17,7 +17,8 @@ package test; import fmt; -type nomem = !void; +// #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; }; diff --git a/test/wcc/990_selfhost.c b/test/wcc/990_selfhost.c index 0b4171a3..cd0fc7cb 100644 --- a/test/wcc/990_selfhost.c +++ b/test/wcc/990_selfhost.c @@ -447,6 +447,16 @@ probe_ww_compile(const char *bin) " o.p = in_;\n" " return o.p.val: i32;\n" "};", 42 }, + /* #29: `nomem` predeclared in the universe scope as a TYPE + * must not block a value of the same name. scope_lookup is + * kind-aware, so `let nomem: i32 = 42;` shadows the type in + * value position. Byte-identity vs cstage proves both stages + * route the value-position lookup past the universe-scope + * type seed. */ + { "fn main() i32 = {\n" + " let nomem: i32 = 42;\n" + " return nomem;\n" + "};", 42 }, { NULL, 0 }, }; char rt[1024];