wcc/ww: mangle imported symbols on dotted import path (#22 M1, #32)

Switch symbol mangling from the import leaf clause to the full dotted import path for directory packages; single-file imports keep package-clause mangling (isdir-gate: imported<=>directory-import). The root build unit's fn main stays bare, every other top-level decl mangles, closing #31's duplicate-main hazard by construction (#32). Both stages, byte-identical.

Single commit, not split: the bare rename (f244af3) is red on its own because it unmasks cross-module resolution gaps that do not reproduce pre-M1, so the fixes are intrinsic to making the rename correct. Included: wwstage fnret/fnparamslookupmod map import alias->path (#199b cross-module union-variant scrutinee resolved the wrong fn's union); cstage use_path prefers the referencing module's import for an ambiguous leaf alias (sha256 crypto.math vs strconv math). Tests table-driven: 989_m1mangle_run/_sym, 989_m1union_run (gate-visible per-arm exit codes + cs==ww byte-id).
This commit is contained in:
2026-06-15 17:37:18 +09:00
parent 64d6c15e41
commit f308818b4b
30 changed files with 1851 additions and 241 deletions

View File

@@ -1,3 +1,4 @@
//ww:module time
// time — clocks, instants, durations. Mirrors Hare's lib/time
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
// +linux/functions.ha). Calendar / date / strftime / timezone /
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
return 0i8;
};
//ww:module rt
// rt — runtime primitives exposed to ww programs.
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
@@ -118,6 +120,7 @@ package rt;
// a future task (task #39). ref/hare/rt/malloc.ha:27.
@symbol("rt_malloc") export fn malloc(n: u64) *void;
//ww:module os
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
return r >= 0i64;
};
//ww:module types
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
// Hare's split between types::limits and math::.
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
def RUNE_MIN: rune = '\0';
//ww:module bytes
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
};
};
//ww:module encoding.utf8
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
};
//ww:module strings
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
//
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
return frombytes(buf);
};
//ww:module-reset
// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
@@ -3086,6 +3094,7 @@ type asm_ = struct {
errs: i32,
};
//ww:module-reset
// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c.
//
// Character-level helpers for w6a's line-oriented parser. The parser
@@ -3164,6 +3173,7 @@ export fn parsenum(p: *u8, n: u64) (i64, u64) = {
return v, i;
};
//ww:module-reset
// selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c.
//
// Line-oriented parser for the asm subset emitted by w6c.
@@ -3764,6 +3774,7 @@ export fn parse(a: *asm_) i32 = {
return a.errs;
};
//ww:module-reset
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c.
//
// Encode the parsed aprog list into amd64 machine bytes, appending to
@@ -4620,6 +4631,7 @@ export fn encode(a: *asm_) i32 = {
return a.errs;
};
//ww:module-reset
// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c.
//
// Emit a tiny ELF64 relocatable object. Layout (in file order):
@@ -5033,6 +5045,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
return 0;
};
//ww:module-reset
// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c.
//
// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o.

View File

@@ -1,3 +1,4 @@
//ww:module time
// time — clocks, instants, durations. Mirrors Hare's lib/time
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
// +linux/functions.ha). Calendar / date / strftime / timezone /
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
return 0i8;
};
//ww:module rt
// rt — runtime primitives exposed to ww programs.
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
@@ -118,6 +120,7 @@ package rt;
// a future task (task #39). ref/hare/rt/malloc.ha:27.
@symbol("rt_malloc") export fn malloc(n: u64) *void;
//ww:module os
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
return r >= 0i64;
};
//ww:module types
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
// Hare's split between types::limits and math::.
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
def RUNE_MIN: rune = '\0';
//ww:module bytes
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
};
};
//ww:module encoding.utf8
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
};
//ww:module strings
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
//
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
return frombytes(buf);
};
//ww:module strconv
// strconv — arbitrary-precision decimal engine for float↔string
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
@@ -3182,6 +3190,7 @@ fn decimal_round(d: *decimal) u64 = {
return n;
};
//ww:module math
// floats — f64 classification, sign, bit-reinterpret core, and the f64
// decompose half (subnormal-normalize + frexp). Ported from
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
@@ -3456,6 +3465,7 @@ export fn frexpf64(n: f64) (f64, i64) = {
return (mantissa, exp);
};
//ww:module math
// math — numeric helpers. Subset of Hare's math::; only the absolute-
// value pair for the signed integer types we currently care about. The
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
@@ -3472,6 +3482,7 @@ export fn absi64(n: i64) u64 = {
return n: u64;
};
//ww:module strconv
// strconv — float→string via Ryū (shortest round-trippable decimal).
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
@@ -4266,6 +4277,7 @@ export fn f32tos(n: f32) str = {
return r;
};
//ww:module strconv
// strconv — Ryū float→string lookup tables + bit-count constants.
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
@@ -4377,6 +4389,7 @@ let POW5_TABLE: [26]u64 = [
59604644775390625u64, 298023223876953125u64,
];
//ww:module ascii
// ascii — rune-class predicates and case folding for the ASCII range.
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
// outside 0..127 always answer `false`. The lexer hot path uses these
@@ -4581,6 +4594,7 @@ export fn strupper_buf(s: str, buf: []u8) (str | nomem) = {
return strings.frombytes(buf);
};
//ww:module strconv
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
@@ -5280,6 +5294,7 @@ export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
return 0: invalid; // unreachable (path-cov)
};
//ww:module strconv
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
@@ -5979,6 +5994,7 @@ let powers_of_ten: [596][2]u64 = [
[0x73832EEC6FFF3111u64, 0xD226FC195C6A2F8Cu64],
];
//ww:module strconv
// strconv — number↔string conversions.
//
// Mirrors Hare's strconv:: surface. The *tos functions return a
@@ -6397,6 +6413,7 @@ export fn strerror(e: error) str = {
return strings.dup("");
};
//ww:module-reset
// lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
// Tok / Pos shapes from cmd/wcc/ww.h.
//
@@ -6520,7 +6537,10 @@ type tkind = enum i32 {
TK_MODRESET = 87, // `//ww:module-reset` driver bundle boundary:
// reset curmod to "" before a package-less file
// (#16 option-B; cstage TK_MODRESET twin)
TK_LAST = 88,
TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import
// boundary; decls mangle on the path, not the
// leaf `package` clause (M1 #22; cstage twin)
TK_LAST = 89,
};
// ---- Pos / Tok --------------------------------------------------------
@@ -6643,6 +6663,7 @@ export fn tokname(k: tkind) str = {
case tkind.TK_ENUM: return "enum";
case tkind.TK_MODULE: return "package";
case tkind.TK_MODRESET: return "//ww:module-reset";
case tkind.TK_MODPATH: return "//ww:module";
case tkind.TK_LPAREN: return "(";
case tkind.TK_RPAREN: return ")";
@@ -6797,6 +6818,7 @@ export fn tokprint(fd: i32, t: *tok) void = {
fputcbyte(fd, '\n');
};
//ww:module lex
// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c.
//
// The DFA, the helpers, and the order of decisions all mirror the C
@@ -6858,6 +6880,10 @@ type lex = struct {
// a `//ww:module-reset` directive was seen in the last skipped run;
// lexnext emits TK_MODRESET before the next real token (#16 opt-B).
modreset: i32,
// a `//ww:module <path>` directive was seen in the last skipped run;
// lexnext emits TK_MODPATH carrying this dotted path (M1 #22).
modpathset: i32,
modpath: str,
};
export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
@@ -6869,6 +6895,7 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
l.col = 1;
l.errs = 0;
l.modreset = 0;
l.modpathset = 0;
};
// srcb — byte at offset; helper that lifts the cast out of indexing.
@@ -6936,19 +6963,62 @@ fn skipws(l: *lex) bool = {
// The body is then skipped like any comment.
// Mirrors cstage lex.c skipws. Compare via lpeek
// (no consume) so the skip loop below is unchanged.
let dir: str = "ww:module-reset";
let pre: str = "ww:module";
let di: i32 = 0;
let matched: bool = true;
for (di < dir.len) {
if (lpeek(l, di: u64) != dir[di]: i32) {
for (di < pre.len) {
if (lpeek(l, di: u64) != pre[di]: i32) {
matched = false; break;
};
di += 1;
};
if (matched) {
let nx: i32 = lpeek(l, dir.len: u64);
if (nx == '\n') { l.modreset = 1; }
else { if (nx < 0) { l.modreset = 1; }; };
let nx: i32 = lpeek(l, pre.len: u64);
if (nx == '-') {
let rest: str = "-reset";
let rj: i32 = 0;
let rm: bool = true;
for (rj < rest.len) {
if (lpeek(l, (pre.len + rj): u64)
!= rest[rj]: i32) {
rm = false; break;
};
rj += 1;
};
if (rm) {
let af: i32 = lpeek(l,
(pre.len + rest.len): u64);
if (af == '\n') { l.modreset = 1; }
else { if (af < 0) { l.modreset = 1; }; };
};
} else { if (nx == ' ' || nx == '\t') {
// `//ww:module <path>` — M1 import boundary.
let k: u64 = pre.len: u64;
for (true) {
let sc: i32 = lpeek(l, k);
if (sc == ' ' || sc == '\t') {
k += 1u64; continue;
};
break;
};
let s0: u64 = k;
for (true) {
let pc: i32 = lpeek(l, k);
if (pc < 0) { break; };
if (pc == '\n' || pc == '\r'
|| pc == ' ' || pc == '\t') {
break;
};
k += 1u64;
};
if (k > s0) {
let view: str;
view.ptr = l.src + l.lpos + s0;
view.len = (k - s0): i32;
l.modpath = strings.dup(view);
l.modpathset = 1;
};
}; };
};
for (true) {
let cx: i32 = lpeek(l, 0u64);
@@ -7462,6 +7532,12 @@ export fn lexnext(l: *lex, out: *tok) void = {
emitsimple(&start, tkind.TK_MODRESET, out);
return;
};
if (l.modpathset != 0) {
l.modpathset = 0;
emitsimple(&start, tkind.TK_MODPATH, out);
out.text = l.modpath;
return;
};
if (!more) {
emitsimple(&start, tkind.TK_EOF, out);
return;
@@ -7578,6 +7654,7 @@ export fn lexnext(l: *lex, out: *tok) void = {
out.text = strings.dup(view);
};
//ww:module-reset
// lib/ww/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
//
// Status: AST printer is fully ported. Constructor `newnode` is here.
@@ -7717,13 +7794,17 @@ type node = struct {
type_: *void, // filled in by checker; type.ww treats it as *tinfo
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
nmod: str, // originating module from `// MODULE: foo`; "" if none
usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias
// in `str` (M1 #22); "" otherwise
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
// boundary (vs root/primary); gates root-only bare main
};
export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
// to tokenise identically through C and ww (lex.ww:382 has the
// same workaround for the cstage %g-formats vs ww-skips divergence).
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod=""})!;
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
return n;
};
@@ -7965,11 +8046,13 @@ export fn astprint(fd: i32, n: *node) void = {
pr(fd, n, 0);
};
//ww:module parse
// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww.
package parse;
import os;
import strings;
import tok;
// `import encoding.utf8;` — the driver resolves the dotted path to
@@ -7984,13 +8067,19 @@ fn parseuse(p: *parser) *node = {
advance(p); // past `use`
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
n.nmod = p.curmod;
// M1 #22: accumulate the full dotted import path (n.usepath) for the
// checker's path-keyed module match; n.str stays the leaf alias the
// user writes (`utf8.x`).
let leaf: str;
expectident(p, &leaf);
let path: str = leaf;
for (p.curkind == tkind.TK_DOT) {
advance(p); // past `.`
expectident(p, &leaf);
path = strings.concat(path, ".", leaf);
};
n.str = leaf;
n.usepath = path;
expecttok(p, tkind.TK_SEMI, "expected ';' after use");
return n;
};
@@ -8147,6 +8236,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
};
//ww:module parse
// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww.
package parse;
@@ -8616,6 +8706,7 @@ fn parseexpr(p: *parser) *node = {
};
//ww:module parse
// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
//
// Split into Hare-style submodule: parse.ww (here) holds the parser
@@ -8634,6 +8725,7 @@ package parse;
// dir-enum when callers `import parse;` (which dir-enums
// lib/ww/parse/).
import os;
import strings;
import tok;
type parser = struct {
@@ -8661,6 +8753,10 @@ type parser = struct {
// multi-file streams successive `module` decls mark per-file
// section boundaries. Mirrors cstage Parser.curmod.
curmod: str,
// M1 #22: active `//ww:module <path>` dotted import path. While set,
// decls stamp nmod=pathmod and imported=1, and the in-file `package`
// clause is an assertion. "" means inactive (root/primary).
pathmod: str,
};
fn refill(p: *parser) void = {
@@ -8680,6 +8776,7 @@ export fn parserinit(p: *parser, l: *lex) void = {
p.l = l;
p.errs = 0;
p.nocast = 0;
p.pathmod = "";
refill(p);
};
@@ -9026,7 +9123,30 @@ export fn parsefile(p: *parser) *node = {
let name: str;
expectident(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
p.curmod = name;
if (p.pathmod.len != 0) {
// M1 #22: while an import path is active the in-file
// `package` clause is an ASSERTION — its leaf must
// equal the path's last component; it does NOT
// overwrite the path-derived module.
let (pre, post) = strings.rcut(p.pathmod, ".");
let last: str = post;
if (post.len == 0) { last = p.pathmod; };
if (strings.compare(name, last) != 0) {
errmsg(p, "package does not match import path");
};
} else {
p.curmod = name;
};
continue;
};
// `//ww:module <path>` — M1 #22 import boundary. The following
// file's decls mangle on the full dotted import path, not the
// leaf `package` clause, and are flagged imported (gates the
// root-only bare-`main` rule, #32).
if (p.curkind == tkind.TK_MODPATH) {
p.pathmod = p.curtext;
p.curmod = p.curtext;
advance(p);
continue;
};
// `//ww:module-reset` — bundle boundary before a package-less
@@ -9043,6 +9163,7 @@ export fn parsefile(p: *parser) *node = {
empty.ptr = nil;
empty.len = 0;
p.curmod = empty;
p.pathmod = "";
continue;
};
let attrs = parseattrs(p);
@@ -9094,6 +9215,9 @@ export fn parsefile(p: *parser) *node = {
};};};};};};
if (d != nil) {
// M1 #22: flag decls reached via an import-path boundary
// (gates the root-only bare-`main` rule, #32).
if (p.pathmod.len != 0) { d.imported = 1; };
if (head == nil) {
head = d;
tail = d;
@@ -9107,6 +9231,7 @@ export fn parsefile(p: *parser) *node = {
return f;
};
//ww:module parse
// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww.
package parse;
@@ -9521,6 +9646,7 @@ fn parsestmt(p: *parser) *node = {
};
//ww:module-reset
// lib/ww/typ.ww — port of cmd/wcc/type.c.
//
// Status: full structural port. The C version uses module-globals for
@@ -10137,6 +10263,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
return true; // primitives match by kind alone
};
//ww:module-reset
// lib/ww/sym.ww — port of cmd/wcc/sym.c.
//
// Per-scope hashtable, chained to the parent. Lookup walks up.
@@ -10472,6 +10599,7 @@ export fn scopesamekeysym(s: *scope, name: str, mod: str) *sym = {
return nil;
};
//ww:module-reset
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
//
// Status: name-resolution + primitive-type seeding only. Full type
@@ -10638,14 +10766,48 @@ fn declmod(file: *node, d: *node) str = {
if (file == nil) { return empty; };
let u: *node = file.list;
for (u != nil) {
// M1 #22: a decl is imported iff some `use` directive's full
// dotted import path equals the decl's module (now the path).
// Single-level packages have usepath == leaf so this is
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
if (u.kind == nkind.N_USE) {
if (streq(u.str, d.nmod)) { return d.nmod; };
if (streq(u.usepath, d.nmod)) { return d.nmod; };
};
u = u.next;
};
return empty;
};
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
// to the full dotted import path it binds (`encoding.utf8`), for the
// module-qualified resolution and codegen hint (M1 #22). Single-level
// packages have usepath == alias so the result is unchanged. The
// current tree has one occurrence per leaf, so the map is unambiguous.
fn usepathfor(file: *node, alias: str) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
let u: *node = file.list;
for (u != nil) {
if (u.kind == nkind.N_USE) {
if (streq(u.str, alias)) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
};
u = u.next;
};
return empty;
};
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
fn modkeyfor(c: *checker, alias: str) str = {
let mk: str = usepathfor(c.file, alias);
if (mk.len == 0) { return alias; };
return mk;
};
// srcimports — does the source file that contributed decl-module
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
// `modtag.len == 0` means primary, matching declmod's empty-str
@@ -10662,7 +10824,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
// module bareword and lib/fmt's own
// `fn bsprintf(fmt: str, ...)` is not a shadow.
if (u.nmod.len > 0) {
if (streq(u.nmod, u.str)) {
if (streq(u.nmod, u.usepath)) {
u = u.next;
continue;
};
@@ -10751,7 +10913,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
// pulls lack import->file->symbol provenance). Message byte-identical
// to cstage check.c.
if (k == nkind.N_USE) {
if (mod.len != 0 && streq(nm, mod)) {
// M1 #22: self-import ⟺ the imported path equals the use's own
// (owning) module path. Compares paths, not leaves.
if (mod.len != 0 && streq(d.usepath, mod)) {
cerr("self-import: package '"); cerr(mod);
cerr("' cannot import itself\n"); c.errs += 1i32;
};
@@ -10960,7 +11124,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
let leaf: str;
leaf.ptr = nm.ptr + (dot + 1): u64;
leaf.len = nm.len - (dot + 1);
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
};
};
};
@@ -11358,7 +11522,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
@@ -11609,7 +11773,7 @@ fn scruttype(c: *checker, e: *node) *node = {
if (e.kind == nkind.N_DOT) {
if (e.lhs == nil) { return nil; };
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
if (s == nil) { return nil; };
if (s.decl == nil) { return nil; };
return s.decl.lhs;
@@ -12148,7 +12312,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
if (k == nkind.N_DOT) {
if (n.lhs == nil) { return false; };
if (n.lhs.kind != nkind.N_IDENT) { return false; };
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
if (s == nil) { return false; };
if (s.skind != skind.SK_DEF) { return false; };
if (s.decl == nil) { return false; };
@@ -13265,7 +13429,7 @@ fn unoptype(c: *checker, e: *node) *node = {
// cstage's actual acceptance reason.
if (e.lhs.kind == nkind.N_DOT) {
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) {
if (fs.decl != nil) {
@@ -13923,7 +14087,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// global-leaf path) and harec check_autodereference
// (ref/harec/src/check.c:1566-1581).
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
};
};
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
@@ -13983,7 +14147,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// qualified resolution and the lenient checker policy
// keeps the silent miss documented at scruttype L656.
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
if (fs != nil) { if (fs.decl != nil) {
// #34: a module-qualified bare fn rvalue `mod.fn` types as
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
@@ -15840,7 +16004,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
};
if (ms != nil) {
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
@@ -16794,29 +16958,25 @@ export fn checkfile(c: *checker, file: *node) void = {
d = d.next;
};
// Program-global, name-only, cross-module uniqueness on `main`.
// `main` lowers to ONE bare entry symbol, so a second top-level
// decl named `main` (any kind, any package) collides with the
// entry at link time — today a silent segfault / link-fail in
// both stages. The (name, module) duplicate rejects in installtop
// read a cross-package `foo.main` and the bare entry as distinct,
// so they miss this. Correct multi-main mangling (entry stays bare,
// the rest qualify) is deferred (task #32); reject loudly meanwhile
// (rule 7). Walks USER decls only — runs before the -T synth main
// is appended below — so a hosted-test build never false-counts.
// Twin of cmd/wcc/check.c.
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
// the ROOT-unit main (imported==0) — it alone lowers to the bare
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
// collision by construction (#31). Two ROOT entries still collide on
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
let firstmain: *node = nil;
let mm: *node = file.list;
for (mm != nil) {
let ismain: bool = (mm.kind == nkind.N_FNDECL
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
if (ismain) {
if (ismain && mm.imported == 0) {
if (firstmain == nil) {
firstmain = mm;
} else {
cerr(mm.file);
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
c.errs += 1;
};
};
@@ -17146,6 +17306,7 @@ export fn checkfile(c: *checker, file: *node) void = {
};
//ww:module io
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
//
// No closures, no methods. A `stream` is a pointer to a `vtable` of
@@ -17173,6 +17334,7 @@ export type eof = void;
// underlying buffer-length type is i32 today.
export type underread = !i32;
//ww:module io
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
//
// The single io stream surface (the fold-eFinal collapse retired the
@@ -17396,6 +17558,7 @@ export fn empty() stream = {
return &_empty_vt;
};
//ww:module errors
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
//
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
@@ -17532,6 +17695,7 @@ fn rt_strerror(op: *opaque_data) str = {
return os.strerror(*e);
};
//ww:module io
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).
@@ -17617,6 +17781,7 @@ export type closer = fn(s: stream) (void | error);
// `stream` directly, mirroring the reader/writer/closer aliases above.
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
//ww:module memio
// memio — in-memory io stream. Project #94 fold-eFinal.
//
// Hare's memio:: surface, drop underscores. Two flavours behind a
@@ -17922,6 +18087,7 @@ export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
return r;
};
//ww:module-reset
// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww.
//
// General helpers used across cgenexpr / cgenstmt / cgendecl:
@@ -20091,10 +20257,14 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: the embedded qualifier (`utf8`) is the use ALIAS;
// the struct's smod is now the dotted import PATH
// (`encoding.utf8`). Map alias→path before comparing.
let pkgmod: str = usehint(c, pkg);
let b: *structinfo = c.structs;
for (b != nil) {
if (streq(b.sname, leaf)) {
if (streq(b.smod, pkg)) {
if (streq(b.smod, pkgmod)) {
return b;
};
};
@@ -23474,6 +23644,7 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
};
//ww:module-reset
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
//
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
@@ -27782,10 +27953,10 @@ fn cgdot(c: *cgen, n: *node) void = {
// lhs.str is the explicit module hint so a same-leaf
// def in another module (head of c.fnrets) can't shadow
// the explicit qualifier (#17 N_DOT-arm omission audit).
let frt: *node = fnretlookupmod(c, fld, lhs.str);
let frt: *node = fnretlookupmod(c, fld, usehint(c, lhs.str));
if (frt != nil) {
emitline("\tLEAQ\t");
emitfnname(c, fld, lhs.str);
emitfnname(c, fld, usehint(c, lhs.str));
emitline("(SB), AX\n");
return;
};
@@ -27797,7 +27968,7 @@ fn cgdot(c: *cgen, n: *node) void = {
// the explicit module hint — a 3rd-module qualifier
// `alpha.MSG` from gamma needs alpha (not c.curmod)
// to beat a head-of-c.defs beta.MSG collision (#11).
let drhs: *node = deflookuprhsmod(c, fld, lhs.str);
let drhs: *node = deflookuprhsmod(c, fld, usehint(c, lhs.str));
if (drhs != nil) {
if (drhs.kind == nkind.N_STRLIT) {
let bytes: str = drhs.str;
@@ -27818,11 +27989,11 @@ fn cgdot(c: *cgen, n: *node) void = {
// threads lhs.str via emitfnname.
if (streq(mqop, "MOVQ")) {
emitline("\tMOVQ\t");
emitsymnamehint(c, fld, lhs.str);
emitsymnamehint(c, fld, usehint(c, lhs.str));
emitline("(SB), AX\n");
} else {
emitline("\tLEAQ\t");
emitsymnamehint(c, fld, lhs.str);
emitsymnamehint(c, fld, usehint(c, lhs.str));
emitline("(SB), CX\n");
emitline("\t");
emitline(mqop);
@@ -28702,10 +28873,10 @@ fn cgun(c: *cgen, n: *node) void = {
// pre-#149 gap (file as #150-family).
if (!isletvar(c, basenm) && !deflookup(c, basenm)) {
let fld: str = opnd.str;
let frt: *node = fnretlookupmod(c, fld, basenm);
let frt: *node = fnretlookupmod(c, fld, usehint(c, basenm));
if (frt != nil) {
emitline("\tLEAQ\t");
emitfnname(c, fld, basenm);
emitfnname(c, fld, usehint(c, basenm));
emitline("(SB), AX\n");
return;
};
@@ -28714,7 +28885,7 @@ fn cgun(c: *cgen, n: *node) void = {
// &aa.v takes aa's global, not a
// same-leaf collision.
emitline("\tLEAQ\t");
emitsymnamehint(c, fld, basenm);
emitsymnamehint(c, fld, usehint(c, basenm));
emitline("(SB), AX\n");
return;
};
@@ -31703,7 +31874,7 @@ fn cgcall(c: *cgen, n: *node) void = {
hint.len = 0;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
hint = callee.lhs.str;
hint = usehint(c, callee.lhs.str);
};
};
emitfnname(c, calleename, hint);
@@ -36013,6 +36184,7 @@ fn cgassign(c: *cgen, n: *node) void = {
//ww:module-reset
// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww.
//
// cgstmt is a thin dispatcher over n.kind; each branch defers to a
@@ -40362,6 +40534,7 @@ fn cgcontinue(c: *cgen, n: *node) void = {
//ww:module-reset
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
//
// Houses the top-level emission glue:
@@ -40966,9 +41139,17 @@ fn cgfn(c: *cgen, fn_: *node) void = {
// Emit the TEXT label via emitfnname so the def site picks up the
// same skip rule (FFI / `main` / empty-module) and the same module
// hint (this fn's own module) that the call sites use.
// hint (this fn's own module) that the call sites use. M1 #32: the
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
// bare directly, mirroring collectmods' skip; without this its
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
// onto an IMPORTED package's now-registered `pkg.main`.
emitline("TEXT ");
emitfnname(c, fn_.str, fn_.nmod);
if (streq(fn_.str, "main") && fn_.imported == 0) {
emitbytes(fn_.str.ptr, fn_.str.len: u64);
} else {
emitfnname(c, fn_.str, fn_.nmod);
};
emitline(",$");
emitint(frame: i64);
emitline("\n");
@@ -41015,6 +41196,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
emitletdataw(c, file);
};
//ww:module-reset
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
//
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
@@ -41140,10 +41322,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *aliasent = c.aliases;
for (b != nil) {
if (streq(b.aname, leaf)) {
if (streq(b.amod, pkg)) {
if (streq(b.amod, pkgmod)) {
return b.target;
};
};
@@ -41328,10 +41513,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *enumtype = c.enums;
for (b != nil) {
if (streq(b.ename, leaf)) {
if (streq(b.emod, pkg)) {
if (streq(b.emod, pkgmod)) {
return b;
};
};
@@ -41502,6 +41690,9 @@ type cgen = struct {
enums: *enumtype,
mods: *modent, // fn (any export status) + non-exported
// let/def/type decls → originating module
uses: *modent, // M1 #22: N_USE alias → dotted import path,
// for the qualified-ref codegen hint
// (mname=alias, nmod=path)
lets: *letvar, // top-level mutable scalar `let` bindings
fnname: str,
curmod: str, // current fn's `// MODULE: foo` directive (len=0
@@ -43877,7 +44068,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
// the leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (ev.lhs.kind == nkind.N_DOT) {
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
} else {
emitfnname(c, ev.lhs.str, c.curmod);
};
@@ -44064,7 +44255,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
// leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (r.lhs.kind == nkind.N_DOT) {
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
} else {
emitfnname(c, r.lhs.str, c.curmod);
};
@@ -44535,11 +44726,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
// wrote (`utf8`); fn decls register f.fmod under the dotted import
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
// matches its own module instead of falling back to the name-only
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
// utf8.next) otherwise wins, resolving a match scrutinee to the
// caller's union and collapsing arms 2+. usehint is idempotent on a
// path / c.curmod (returns the input when no `use` matches), so the
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
// c.curmod callers are unaffected. The choke-point twin of the
// struct/alias/enum usehint splitters (cgen.ww:128/319,
// cgenutil.ww:2173) — closes the whole fnret class by construction.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.rtype; };
if (streq(f.fmod, mk)) { return f.rtype; };
};
f = f.frnext;
};
@@ -44599,11 +44803,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
// matching module is registered — mirrors aliaslookup's two-pass shape
// (cgen.ww:75, fixed in #27).
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): map import alias -> dotted path, identical to
// fnretlookupmod (the param-side twin). usehint is idempotent on a
// path / c.curmod so existing callers are unaffected.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.params; };
if (streq(f.fmod, mk)) { return f.params; };
};
f = f.frnext;
};
@@ -44783,9 +44991,17 @@ type modent = struct {
fn collectmods(c: *cgen, file: *node) void = {
c.mods = nil;
c.uses = nil;
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
c.uses = um;
};
};
// Mirror collectfnrets' shape exactly (plain prepend in one
// branch). Earlier nested-if/early-return variants tickled a
// wwstage cgen bug that dropped most prepends.
@@ -44803,7 +45019,9 @@ fn collectmods(c: *cgen, file: *node) void = {
a = a.next;
};
if (!isffi) {
if (!streq(d.str, "main")) {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
@@ -44850,6 +45068,19 @@ fn modlookup(c: *cgen, name: str) str = {
return empty;
};
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
m = m.mnext;
};
return alias;
};
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
// preferring entries where module matches `hint`; falls back to the
// first leaf-name match when nothing matches the hint (legacy single-
@@ -45041,6 +45272,7 @@ export fn fargregname(i: i32) str = {
return "?";
};
//ww:module-reset
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c.
//
// w6c = amd64 compiler. Read .ww, parse, codegen, emit Plan 9 amd64

View File

@@ -1,3 +1,4 @@
//ww:module time
// time — clocks, instants, durations. Mirrors Hare's lib/time
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
// +linux/functions.ha). Calendar / date / strftime / timezone /
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
return 0i8;
};
//ww:module rt
// rt — runtime primitives exposed to ww programs.
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
@@ -118,6 +120,7 @@ package rt;
// a future task (task #39). ref/hare/rt/malloc.ha:27.
@symbol("rt_malloc") export fn malloc(n: u64) *void;
//ww:module os
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
return r >= 0i64;
};
//ww:module types
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
// Hare's split between types::limits and math::.
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
def RUNE_MIN: rune = '\0';
//ww:module bytes
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
};
};
//ww:module encoding.utf8
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
};
//ww:module strings
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
//
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
return frombytes(buf);
};
//ww:module-reset
// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c.
//
// Linker symbol table. Singly-linked list, usually a few hundred
@@ -2961,6 +2969,7 @@ export fn lookup(l: *lnk, name: str) *lsym = {
return nil;
};
//ww:module-reset
// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c.
//
// Loads relocatable ELF64 .o files emitted by w6a, appends .text to
@@ -3580,6 +3589,7 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
return 0;
};
//ww:module-reset
// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c.
//
// Load a shared object (ET_DYN) so the linker knows which symbols it
@@ -3983,6 +3993,7 @@ export fn soversion(so: *lso, name: str) str = {
return result;
};
//ww:module-reset
// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c.
//
// Resolution + relocation. l_resolve flags every undefined symbol
@@ -4115,6 +4126,7 @@ export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = {
return l.errs;
};
//ww:module-reset
// selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c.
//
// Emit a dynamic-linked ELF executable. The shape is the simplest
@@ -4867,6 +4879,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
return 0;
};
//ww:module-reset
// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c.
//
// Emit a static ELF64 executable. File layout (per the C original):
@@ -5048,6 +5061,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
return 0;
};
//ww:module-reset
// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c.
//
// w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar`

View File

@@ -123,10 +123,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *aliasent = c.aliases;
for (b != nil) {
if (streq(b.aname, leaf)) {
if (streq(b.amod, pkg)) {
if (streq(b.amod, pkgmod)) {
return b.target;
};
};
@@ -311,10 +314,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *enumtype = c.enums;
for (b != nil) {
if (streq(b.ename, leaf)) {
if (streq(b.emod, pkg)) {
if (streq(b.emod, pkgmod)) {
return b;
};
};
@@ -485,6 +491,9 @@ type cgen = struct {
enums: *enumtype,
mods: *modent, // fn (any export status) + non-exported
// let/def/type decls → originating module
uses: *modent, // M1 #22: N_USE alias → dotted import path,
// for the qualified-ref codegen hint
// (mname=alias, nmod=path)
lets: *letvar, // top-level mutable scalar `let` bindings
fnname: str,
curmod: str, // current fn's `// MODULE: foo` directive (len=0
@@ -2860,7 +2869,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
// the leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (ev.lhs.kind == nkind.N_DOT) {
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
} else {
emitfnname(c, ev.lhs.str, c.curmod);
};
@@ -3047,7 +3056,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
// leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (r.lhs.kind == nkind.N_DOT) {
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
} else {
emitfnname(c, r.lhs.str, c.curmod);
};
@@ -3518,11 +3527,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
// wrote (`utf8`); fn decls register f.fmod under the dotted import
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
// matches its own module instead of falling back to the name-only
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
// utf8.next) otherwise wins, resolving a match scrutinee to the
// caller's union and collapsing arms 2+. usehint is idempotent on a
// path / c.curmod (returns the input when no `use` matches), so the
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
// c.curmod callers are unaffected. The choke-point twin of the
// struct/alias/enum usehint splitters (cgen.ww:128/319,
// cgenutil.ww:2173) — closes the whole fnret class by construction.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.rtype; };
if (streq(f.fmod, mk)) { return f.rtype; };
};
f = f.frnext;
};
@@ -3582,11 +3604,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
// matching module is registered — mirrors aliaslookup's two-pass shape
// (cgen.ww:75, fixed in #27).
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): map import alias -> dotted path, identical to
// fnretlookupmod (the param-side twin). usehint is idempotent on a
// path / c.curmod so existing callers are unaffected.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.params; };
if (streq(f.fmod, mk)) { return f.params; };
};
f = f.frnext;
};
@@ -3766,9 +3792,17 @@ type modent = struct {
fn collectmods(c: *cgen, file: *node) void = {
c.mods = nil;
c.uses = nil;
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
c.uses = um;
};
};
// Mirror collectfnrets' shape exactly (plain prepend in one
// branch). Earlier nested-if/early-return variants tickled a
// wwstage cgen bug that dropped most prepends.
@@ -3786,7 +3820,9 @@ fn collectmods(c: *cgen, file: *node) void = {
a = a.next;
};
if (!isffi) {
if (!streq(d.str, "main")) {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
@@ -3833,6 +3869,19 @@ fn modlookup(c: *cgen, name: str) str = {
return empty;
};
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
m = m.mnext;
};
return alias;
};
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
// preferring entries where module matches `hint`; falls back to the
// first leaf-name match when nothing matches the hint (legacy single-

View File

@@ -602,9 +602,17 @@ fn cgfn(c: *cgen, fn_: *node) void = {
// Emit the TEXT label via emitfnname so the def site picks up the
// same skip rule (FFI / `main` / empty-module) and the same module
// hint (this fn's own module) that the call sites use.
// hint (this fn's own module) that the call sites use. M1 #32: the
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
// bare directly, mirroring collectmods' skip; without this its
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
// onto an IMPORTED package's now-registered `pkg.main`.
emitline("TEXT ");
emitfnname(c, fn_.str, fn_.nmod);
if (streq(fn_.str, "main") && fn_.imported == 0) {
emitbytes(fn_.str.ptr, fn_.str.len: u64);
} else {
emitfnname(c, fn_.str, fn_.nmod);
};
emitline(",$");
emitint(frame: i64);
emitline("\n");

View File

@@ -4306,10 +4306,10 @@ fn cgdot(c: *cgen, n: *node) void = {
// lhs.str is the explicit module hint so a same-leaf
// def in another module (head of c.fnrets) can't shadow
// the explicit qualifier (#17 N_DOT-arm omission audit).
let frt: *node = fnretlookupmod(c, fld, lhs.str);
let frt: *node = fnretlookupmod(c, fld, usehint(c, lhs.str));
if (frt != nil) {
emitline("\tLEAQ\t");
emitfnname(c, fld, lhs.str);
emitfnname(c, fld, usehint(c, lhs.str));
emitline("(SB), AX\n");
return;
};
@@ -4321,7 +4321,7 @@ fn cgdot(c: *cgen, n: *node) void = {
// the explicit module hint — a 3rd-module qualifier
// `alpha.MSG` from gamma needs alpha (not c.curmod)
// to beat a head-of-c.defs beta.MSG collision (#11).
let drhs: *node = deflookuprhsmod(c, fld, lhs.str);
let drhs: *node = deflookuprhsmod(c, fld, usehint(c, lhs.str));
if (drhs != nil) {
if (drhs.kind == nkind.N_STRLIT) {
let bytes: str = drhs.str;
@@ -4342,11 +4342,11 @@ fn cgdot(c: *cgen, n: *node) void = {
// threads lhs.str via emitfnname.
if (streq(mqop, "MOVQ")) {
emitline("\tMOVQ\t");
emitsymnamehint(c, fld, lhs.str);
emitsymnamehint(c, fld, usehint(c, lhs.str));
emitline("(SB), AX\n");
} else {
emitline("\tLEAQ\t");
emitsymnamehint(c, fld, lhs.str);
emitsymnamehint(c, fld, usehint(c, lhs.str));
emitline("(SB), CX\n");
emitline("\t");
emitline(mqop);
@@ -5226,10 +5226,10 @@ fn cgun(c: *cgen, n: *node) void = {
// pre-#149 gap (file as #150-family).
if (!isletvar(c, basenm) && !deflookup(c, basenm)) {
let fld: str = opnd.str;
let frt: *node = fnretlookupmod(c, fld, basenm);
let frt: *node = fnretlookupmod(c, fld, usehint(c, basenm));
if (frt != nil) {
emitline("\tLEAQ\t");
emitfnname(c, fld, basenm);
emitfnname(c, fld, usehint(c, basenm));
emitline("(SB), AX\n");
return;
};
@@ -5238,7 +5238,7 @@ fn cgun(c: *cgen, n: *node) void = {
// &aa.v takes aa's global, not a
// same-leaf collision.
emitline("\tLEAQ\t");
emitsymnamehint(c, fld, basenm);
emitsymnamehint(c, fld, usehint(c, basenm));
emitline("(SB), AX\n");
return;
};
@@ -8227,7 +8227,7 @@ fn cgcall(c: *cgen, n: *node) void = {
hint.len = 0;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
hint = callee.lhs.str;
hint = usehint(c, callee.lhs.str);
};
};
emitfnname(c, calleename, hint);

View File

@@ -2167,10 +2167,14 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: the embedded qualifier (`utf8`) is the use ALIAS;
// the struct's smod is now the dotted import PATH
// (`encoding.utf8`). Map alias→path before comparing.
let pkgmod: str = usehint(c, pkg);
let b: *structinfo = c.structs;
for (b != nil) {
if (streq(b.sname, leaf)) {
if (streq(b.smod, pkg)) {
if (streq(b.smod, pkgmod)) {
return b;
};
};

View File

@@ -164,14 +164,48 @@ fn declmod(file: *node, d: *node) str = {
if (file == nil) { return empty; };
let u: *node = file.list;
for (u != nil) {
// M1 #22: a decl is imported iff some `use` directive's full
// dotted import path equals the decl's module (now the path).
// Single-level packages have usepath == leaf so this is
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
if (u.kind == nkind.N_USE) {
if (streq(u.str, d.nmod)) { return d.nmod; };
if (streq(u.usepath, d.nmod)) { return d.nmod; };
};
u = u.next;
};
return empty;
};
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
// to the full dotted import path it binds (`encoding.utf8`), for the
// module-qualified resolution and codegen hint (M1 #22). Single-level
// packages have usepath == alias so the result is unchanged. The
// current tree has one occurrence per leaf, so the map is unambiguous.
fn usepathfor(file: *node, alias: str) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
let u: *node = file.list;
for (u != nil) {
if (u.kind == nkind.N_USE) {
if (streq(u.str, alias)) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
};
u = u.next;
};
return empty;
};
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
fn modkeyfor(c: *checker, alias: str) str = {
let mk: str = usepathfor(c.file, alias);
if (mk.len == 0) { return alias; };
return mk;
};
// srcimports — does the source file that contributed decl-module
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
// `modtag.len == 0` means primary, matching declmod's empty-str
@@ -188,7 +222,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
// module bareword and lib/fmt's own
// `fn bsprintf(fmt: str, ...)` is not a shadow.
if (u.nmod.len > 0) {
if (streq(u.nmod, u.str)) {
if (streq(u.nmod, u.usepath)) {
u = u.next;
continue;
};
@@ -277,7 +311,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
// pulls lack import->file->symbol provenance). Message byte-identical
// to cstage check.c.
if (k == nkind.N_USE) {
if (mod.len != 0 && streq(nm, mod)) {
// M1 #22: self-import ⟺ the imported path equals the use's own
// (owning) module path. Compares paths, not leaves.
if (mod.len != 0 && streq(d.usepath, mod)) {
cerr("self-import: package '"); cerr(mod);
cerr("' cannot import itself\n"); c.errs += 1i32;
};
@@ -486,7 +522,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
let leaf: str;
leaf.ptr = nm.ptr + (dot + 1): u64;
leaf.len = nm.len - (dot + 1);
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
};
};
};
@@ -884,7 +920,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
@@ -1135,7 +1171,7 @@ fn scruttype(c: *checker, e: *node) *node = {
if (e.kind == nkind.N_DOT) {
if (e.lhs == nil) { return nil; };
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
if (s == nil) { return nil; };
if (s.decl == nil) { return nil; };
return s.decl.lhs;
@@ -1674,7 +1710,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
if (k == nkind.N_DOT) {
if (n.lhs == nil) { return false; };
if (n.lhs.kind != nkind.N_IDENT) { return false; };
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
if (s == nil) { return false; };
if (s.skind != skind.SK_DEF) { return false; };
if (s.decl == nil) { return false; };
@@ -2791,7 +2827,7 @@ fn unoptype(c: *checker, e: *node) *node = {
// cstage's actual acceptance reason.
if (e.lhs.kind == nkind.N_DOT) {
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) {
if (fs.decl != nil) {
@@ -3449,7 +3485,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// global-leaf path) and harec check_autodereference
// (ref/harec/src/check.c:1566-1581).
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
};
};
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
@@ -3509,7 +3545,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// qualified resolution and the lenient checker policy
// keeps the silent miss documented at scruttype L656.
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
if (fs != nil) { if (fs.decl != nil) {
// #34: a module-qualified bare fn rvalue `mod.fn` types as
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
@@ -5366,7 +5402,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
};
if (ms != nil) {
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
@@ -6320,29 +6356,25 @@ export fn checkfile(c: *checker, file: *node) void = {
d = d.next;
};
// Program-global, name-only, cross-module uniqueness on `main`.
// `main` lowers to ONE bare entry symbol, so a second top-level
// decl named `main` (any kind, any package) collides with the
// entry at link time — today a silent segfault / link-fail in
// both stages. The (name, module) duplicate rejects in installtop
// read a cross-package `foo.main` and the bare entry as distinct,
// so they miss this. Correct multi-main mangling (entry stays bare,
// the rest qualify) is deferred (task #32); reject loudly meanwhile
// (rule 7). Walks USER decls only — runs before the -T synth main
// is appended below — so a hosted-test build never false-counts.
// Twin of cmd/wcc/check.c.
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
// the ROOT-unit main (imported==0) — it alone lowers to the bare
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
// collision by construction (#31). Two ROOT entries still collide on
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
let firstmain: *node = nil;
let mm: *node = file.list;
for (mm != nil) {
let ismain: bool = (mm.kind == nkind.N_FNDECL
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
if (ismain) {
if (ismain && mm.imported == 0) {
if (firstmain == nil) {
firstmain = mm;
} else {
cerr(mm.file);
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
c.errs += 1;
};
};

View File

@@ -1,3 +1,4 @@
//ww:module time
// time — clocks, instants, durations. Mirrors Hare's lib/time
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
// +linux/functions.ha). Calendar / date / strftime / timezone /
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
return 0i8;
};
//ww:module rt
// rt — runtime primitives exposed to ww programs.
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
@@ -118,6 +120,7 @@ package rt;
// a future task (task #39). ref/hare/rt/malloc.ha:27.
@symbol("rt_malloc") export fn malloc(n: u64) *void;
//ww:module os
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
return r >= 0i64;
};
//ww:module types
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
// Hare's split between types::limits and math::.
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
def RUNE_MIN: rune = '\0';
//ww:module bytes
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
};
};
//ww:module encoding.utf8
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
};
//ww:module strings
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
//
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
return frombytes(buf);
};
//ww:module-reset
// selfhost/cmd/ww/main.ww — port of cmd/ww/main.c.
//
// The user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue:
@@ -3367,7 +3375,7 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
// after recursive-expanding its top-of-file `import X;` imports.
// Each source declares its own `package <name>;` (parser stamps
// decls).
fn expand(c: *expctx, pathcs: *u8) void = {
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
let plen: u64 = cstrlen(pathcs);
let view: str;
view.ptr = pathcs;
@@ -3400,8 +3408,21 @@ fn expand(c: *expctx, pathcs: *u8) void = {
let ipath: *u8 = locateimport(c.dirs, idp, idn,
&isdir);
if (ipath != nil) {
if (isdir != 0) { expanddir(c, ipath); }
else { expand(c, ipath); };
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
// imports are package boundaries that path-mangle. A
// single-file import (`import opcodes;` → opcodes.ww
// declaring `package w6a`) is an intra-package file-split:
// it keeps its in-file `package` clause as its module
// (no directive → reset → package-clause mangling).
if (isdir != 0) {
let mv: str;
mv.ptr = idp;
mv.len = idn: i32;
let modstr: str = strings.dup(mv);
expanddir(c, ipath, modstr);
} else {
expand(c, ipath, "");
};
} else {
// #16 ENFORCE-driver (rob A): a locate-miss is
// legal when the package is defined INLINE in the
@@ -3437,7 +3458,17 @@ fn expand(c: *expctx, pathcs: *u8) void = {
// `package main`, which would main-prefix them). A packaged file's own
// `package` decl already sets curmod, so it needs nothing — keeping
// the directive out of every tracked combined.ww. (Task #11.)
if (peekpackage(pathcs) == nil) {
if (modpath.len != 0) {
// M1 #22: import-reached file carries its full dotted path so
// codegen mangles symbols on the path, not the leaf clause.
let dm: str = "//ww:module ";
os.writeall(c.out, dm.ptr, dm.len: u64);
os.writeall(c.out, modpath.ptr, modpath.len: u64);
os.writeall(c.out, "\n".ptr, 1u64);
} else {
// Root/primary: always reset so a preceding imported section's
// sticky pathmod is cleared; a packaged primary's own `package`
// clause then sets curmod fresh (pathmod NULL → real clause).
let d: str = "//ww:module-reset\n";
os.writeall(c.out, d.ptr, d.len: u64);
};
@@ -3613,7 +3644,7 @@ fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
// are pulled once. Strict-same-package: all enumerated files must
// declare the same `package <name>;` (task #23 subset; failure
// mode native to dir-enum).
fn expanddir(c: *expctx, dirpath: *u8) void = {
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
let names: **u8;
let n: i32;
names, n = enumeratedir(dirpath);
@@ -3636,7 +3667,7 @@ fn expanddir(c: *expctx, dirpath: *u8) void = {
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
}; };
};
expand(c, fp.ptr);
expand(c, fp.ptr, modpath);
i += 1;
};
};
@@ -3825,12 +3856,12 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
let td: i32 = 0;
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
if (tp != nil) {
if (td != 0) { expanddir(&c, tp); }
else { expand(&c, tp); };
if (td != 0) { expanddir(&c, tp, "test"); }
else { expand(&c, tp, ""); };
};
};
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
else { expand(&c, src); };
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
else { expand(&c, src, ""); };
};
os.close(cf);

View File

@@ -505,7 +505,7 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
// after recursive-expanding its top-of-file `import X;` imports.
// Each source declares its own `package <name>;` (parser stamps
// decls).
fn expand(c: *expctx, pathcs: *u8) void = {
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
let plen: u64 = cstrlen(pathcs);
let view: str;
view.ptr = pathcs;
@@ -538,8 +538,21 @@ fn expand(c: *expctx, pathcs: *u8) void = {
let ipath: *u8 = locateimport(c.dirs, idp, idn,
&isdir);
if (ipath != nil) {
if (isdir != 0) { expanddir(c, ipath); }
else { expand(c, ipath); };
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
// imports are package boundaries that path-mangle. A
// single-file import (`import opcodes;` → opcodes.ww
// declaring `package w6a`) is an intra-package file-split:
// it keeps its in-file `package` clause as its module
// (no directive → reset → package-clause mangling).
if (isdir != 0) {
let mv: str;
mv.ptr = idp;
mv.len = idn: i32;
let modstr: str = strings.dup(mv);
expanddir(c, ipath, modstr);
} else {
expand(c, ipath, "");
};
} else {
// #16 ENFORCE-driver (rob A): a locate-miss is
// legal when the package is defined INLINE in the
@@ -575,7 +588,17 @@ fn expand(c: *expctx, pathcs: *u8) void = {
// `package main`, which would main-prefix them). A packaged file's own
// `package` decl already sets curmod, so it needs nothing — keeping
// the directive out of every tracked combined.ww. (Task #11.)
if (peekpackage(pathcs) == nil) {
if (modpath.len != 0) {
// M1 #22: import-reached file carries its full dotted path so
// codegen mangles symbols on the path, not the leaf clause.
let dm: str = "//ww:module ";
os.writeall(c.out, dm.ptr, dm.len: u64);
os.writeall(c.out, modpath.ptr, modpath.len: u64);
os.writeall(c.out, "\n".ptr, 1u64);
} else {
// Root/primary: always reset so a preceding imported section's
// sticky pathmod is cleared; a packaged primary's own `package`
// clause then sets curmod fresh (pathmod NULL → real clause).
let d: str = "//ww:module-reset\n";
os.writeall(c.out, d.ptr, d.len: u64);
};
@@ -751,7 +774,7 @@ fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
// are pulled once. Strict-same-package: all enumerated files must
// declare the same `package <name>;` (task #23 subset; failure
// mode native to dir-enum).
fn expanddir(c: *expctx, dirpath: *u8) void = {
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
let names: **u8;
let n: i32;
names, n = enumeratedir(dirpath);
@@ -774,7 +797,7 @@ fn expanddir(c: *expctx, dirpath: *u8) void = {
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
}; };
};
expand(c, fp.ptr);
expand(c, fp.ptr, modpath);
i += 1;
};
};
@@ -963,12 +986,12 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
let td: i32 = 0;
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
if (tp != nil) {
if (td != 0) { expanddir(&c, tp); }
else { expand(&c, tp); };
if (td != 0) { expanddir(&c, tp, "test"); }
else { expand(&c, tp, ""); };
};
};
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
else { expand(&c, src); };
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
else { expand(&c, src, ""); };
};
os.close(cf);

View File

@@ -1,3 +1,4 @@
//ww:module time
// time — clocks, instants, durations. Mirrors Hare's lib/time
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
// +linux/functions.ha). Calendar / date / strftime / timezone /
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
return 0i8;
};
//ww:module rt
// rt — runtime primitives exposed to ww programs.
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
@@ -118,6 +120,7 @@ package rt;
// a future task (task #39). ref/hare/rt/malloc.ha:27.
@symbol("rt_malloc") export fn malloc(n: u64) *void;
//ww:module os
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
return r >= 0i64;
};
//ww:module strconv
// strconv — arbitrary-precision decimal engine for float↔string
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
@@ -1178,6 +1182,7 @@ fn decimal_round(d: *decimal) u64 = {
return n;
};
//ww:module math
// floats — f64 classification, sign, bit-reinterpret core, and the f64
// decompose half (subnormal-normalize + frexp). Ported from
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
@@ -1452,6 +1457,7 @@ export fn frexpf64(n: f64) (f64, i64) = {
return (mantissa, exp);
};
//ww:module math
// math — numeric helpers. Subset of Hare's math::; only the absolute-
// value pair for the signed integer types we currently care about. The
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
@@ -1468,6 +1474,7 @@ export fn absi64(n: i64) u64 = {
return n: u64;
};
//ww:module strconv
// strconv — float→string via Ryū (shortest round-trippable decimal).
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
@@ -2262,6 +2269,7 @@ export fn f32tos(n: f32) str = {
return r;
};
//ww:module strconv
// strconv — Ryū float→string lookup tables + bit-count constants.
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
@@ -2373,6 +2381,7 @@ let POW5_TABLE: [26]u64 = [
59604644775390625u64, 298023223876953125u64,
];
//ww:module types
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
// Hare's split between types::limits and math::.
@@ -2417,6 +2426,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
def RUNE_MIN: rune = '\0';
//ww:module bytes
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
@@ -2968,6 +2978,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
};
};
//ww:module encoding.utf8
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
@@ -3425,6 +3436,7 @@ export fn position(d: *decoder) i32 = {
};
//ww:module strings
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
//
@@ -4377,6 +4389,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
return frombytes(buf);
};
//ww:module ascii
// ascii — rune-class predicates and case folding for the ASCII range.
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
// outside 0..127 always answer `false`. The lexer hot path uses these
@@ -4581,6 +4594,7 @@ export fn strupper_buf(s: str, buf: []u8) (str | nomem) = {
return strings.frombytes(buf);
};
//ww:module strconv
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
@@ -5280,6 +5294,7 @@ export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
return 0: invalid; // unreachable (path-cov)
};
//ww:module strconv
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
@@ -5979,6 +5994,7 @@ let powers_of_ten: [596][2]u64 = [
[0x73832EEC6FFF3111u64, 0xD226FC195C6A2F8Cu64],
];
//ww:module strconv
// strconv — number↔string conversions.
//
// Mirrors Hare's strconv:: surface. The *tos functions return a
@@ -6397,6 +6413,7 @@ export fn strerror(e: error) str = {
return strings.dup("");
};
//ww:module-reset
// lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
// Tok / Pos shapes from cmd/wcc/ww.h.
//
@@ -6520,7 +6537,10 @@ type tkind = enum i32 {
TK_MODRESET = 87, // `//ww:module-reset` driver bundle boundary:
// reset curmod to "" before a package-less file
// (#16 option-B; cstage TK_MODRESET twin)
TK_LAST = 88,
TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import
// boundary; decls mangle on the path, not the
// leaf `package` clause (M1 #22; cstage twin)
TK_LAST = 89,
};
// ---- Pos / Tok --------------------------------------------------------
@@ -6643,6 +6663,7 @@ export fn tokname(k: tkind) str = {
case tkind.TK_ENUM: return "enum";
case tkind.TK_MODULE: return "package";
case tkind.TK_MODRESET: return "//ww:module-reset";
case tkind.TK_MODPATH: return "//ww:module";
case tkind.TK_LPAREN: return "(";
case tkind.TK_RPAREN: return ")";
@@ -6797,6 +6818,7 @@ export fn tokprint(fd: i32, t: *tok) void = {
fputcbyte(fd, '\n');
};
//ww:module lex
// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c.
//
// The DFA, the helpers, and the order of decisions all mirror the C
@@ -6858,6 +6880,10 @@ type lex = struct {
// a `//ww:module-reset` directive was seen in the last skipped run;
// lexnext emits TK_MODRESET before the next real token (#16 opt-B).
modreset: i32,
// a `//ww:module <path>` directive was seen in the last skipped run;
// lexnext emits TK_MODPATH carrying this dotted path (M1 #22).
modpathset: i32,
modpath: str,
};
export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
@@ -6869,6 +6895,7 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
l.col = 1;
l.errs = 0;
l.modreset = 0;
l.modpathset = 0;
};
// srcb — byte at offset; helper that lifts the cast out of indexing.
@@ -6936,19 +6963,62 @@ fn skipws(l: *lex) bool = {
// The body is then skipped like any comment.
// Mirrors cstage lex.c skipws. Compare via lpeek
// (no consume) so the skip loop below is unchanged.
let dir: str = "ww:module-reset";
let pre: str = "ww:module";
let di: i32 = 0;
let matched: bool = true;
for (di < dir.len) {
if (lpeek(l, di: u64) != dir[di]: i32) {
for (di < pre.len) {
if (lpeek(l, di: u64) != pre[di]: i32) {
matched = false; break;
};
di += 1;
};
if (matched) {
let nx: i32 = lpeek(l, dir.len: u64);
if (nx == '\n') { l.modreset = 1; }
else { if (nx < 0) { l.modreset = 1; }; };
let nx: i32 = lpeek(l, pre.len: u64);
if (nx == '-') {
let rest: str = "-reset";
let rj: i32 = 0;
let rm: bool = true;
for (rj < rest.len) {
if (lpeek(l, (pre.len + rj): u64)
!= rest[rj]: i32) {
rm = false; break;
};
rj += 1;
};
if (rm) {
let af: i32 = lpeek(l,
(pre.len + rest.len): u64);
if (af == '\n') { l.modreset = 1; }
else { if (af < 0) { l.modreset = 1; }; };
};
} else { if (nx == ' ' || nx == '\t') {
// `//ww:module <path>` — M1 import boundary.
let k: u64 = pre.len: u64;
for (true) {
let sc: i32 = lpeek(l, k);
if (sc == ' ' || sc == '\t') {
k += 1u64; continue;
};
break;
};
let s0: u64 = k;
for (true) {
let pc: i32 = lpeek(l, k);
if (pc < 0) { break; };
if (pc == '\n' || pc == '\r'
|| pc == ' ' || pc == '\t') {
break;
};
k += 1u64;
};
if (k > s0) {
let view: str;
view.ptr = l.src + l.lpos + s0;
view.len = (k - s0): i32;
l.modpath = strings.dup(view);
l.modpathset = 1;
};
}; };
};
for (true) {
let cx: i32 = lpeek(l, 0u64);
@@ -7462,6 +7532,12 @@ export fn lexnext(l: *lex, out: *tok) void = {
emitsimple(&start, tkind.TK_MODRESET, out);
return;
};
if (l.modpathset != 0) {
l.modpathset = 0;
emitsimple(&start, tkind.TK_MODPATH, out);
out.text = l.modpath;
return;
};
if (!more) {
emitsimple(&start, tkind.TK_EOF, out);
return;
@@ -7578,6 +7654,7 @@ export fn lexnext(l: *lex, out: *tok) void = {
out.text = strings.dup(view);
};
//ww:module-reset
// lib/ww/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
//
// Status: AST printer is fully ported. Constructor `newnode` is here.
@@ -7717,13 +7794,17 @@ type node = struct {
type_: *void, // filled in by checker; type.ww treats it as *tinfo
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
nmod: str, // originating module from `// MODULE: foo`; "" if none
usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias
// in `str` (M1 #22); "" otherwise
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
// boundary (vs root/primary); gates root-only bare main
};
export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
// to tokenise identically through C and ww (lex.ww:382 has the
// same workaround for the cstage %g-formats vs ww-skips divergence).
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod=""})!;
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
return n;
};
@@ -7965,11 +8046,13 @@ export fn astprint(fd: i32, n: *node) void = {
pr(fd, n, 0);
};
//ww:module parse
// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww.
package parse;
import os;
import strings;
import tok;
// `import encoding.utf8;` — the driver resolves the dotted path to
@@ -7984,13 +8067,19 @@ fn parseuse(p: *parser) *node = {
advance(p); // past `use`
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
n.nmod = p.curmod;
// M1 #22: accumulate the full dotted import path (n.usepath) for the
// checker's path-keyed module match; n.str stays the leaf alias the
// user writes (`utf8.x`).
let leaf: str;
expectident(p, &leaf);
let path: str = leaf;
for (p.curkind == tkind.TK_DOT) {
advance(p); // past `.`
expectident(p, &leaf);
path = strings.concat(path, ".", leaf);
};
n.str = leaf;
n.usepath = path;
expecttok(p, tkind.TK_SEMI, "expected ';' after use");
return n;
};
@@ -8147,6 +8236,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
};
//ww:module parse
// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww.
package parse;
@@ -8616,6 +8706,7 @@ fn parseexpr(p: *parser) *node = {
};
//ww:module parse
// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
//
// Split into Hare-style submodule: parse.ww (here) holds the parser
@@ -8634,6 +8725,7 @@ package parse;
// dir-enum when callers `import parse;` (which dir-enums
// lib/ww/parse/).
import os;
import strings;
import tok;
type parser = struct {
@@ -8661,6 +8753,10 @@ type parser = struct {
// multi-file streams successive `module` decls mark per-file
// section boundaries. Mirrors cstage Parser.curmod.
curmod: str,
// M1 #22: active `//ww:module <path>` dotted import path. While set,
// decls stamp nmod=pathmod and imported=1, and the in-file `package`
// clause is an assertion. "" means inactive (root/primary).
pathmod: str,
};
fn refill(p: *parser) void = {
@@ -8680,6 +8776,7 @@ export fn parserinit(p: *parser, l: *lex) void = {
p.l = l;
p.errs = 0;
p.nocast = 0;
p.pathmod = "";
refill(p);
};
@@ -9026,7 +9123,30 @@ export fn parsefile(p: *parser) *node = {
let name: str;
expectident(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
p.curmod = name;
if (p.pathmod.len != 0) {
// M1 #22: while an import path is active the in-file
// `package` clause is an ASSERTION — its leaf must
// equal the path's last component; it does NOT
// overwrite the path-derived module.
let (pre, post) = strings.rcut(p.pathmod, ".");
let last: str = post;
if (post.len == 0) { last = p.pathmod; };
if (strings.compare(name, last) != 0) {
errmsg(p, "package does not match import path");
};
} else {
p.curmod = name;
};
continue;
};
// `//ww:module <path>` — M1 #22 import boundary. The following
// file's decls mangle on the full dotted import path, not the
// leaf `package` clause, and are flagged imported (gates the
// root-only bare-`main` rule, #32).
if (p.curkind == tkind.TK_MODPATH) {
p.pathmod = p.curtext;
p.curmod = p.curtext;
advance(p);
continue;
};
// `//ww:module-reset` — bundle boundary before a package-less
@@ -9043,6 +9163,7 @@ export fn parsefile(p: *parser) *node = {
empty.ptr = nil;
empty.len = 0;
p.curmod = empty;
p.pathmod = "";
continue;
};
let attrs = parseattrs(p);
@@ -9094,6 +9215,9 @@ export fn parsefile(p: *parser) *node = {
};};};};};};
if (d != nil) {
// M1 #22: flag decls reached via an import-path boundary
// (gates the root-only bare-`main` rule, #32).
if (p.pathmod.len != 0) { d.imported = 1; };
if (head == nil) {
head = d;
tail = d;
@@ -9107,6 +9231,7 @@ export fn parsefile(p: *parser) *node = {
return f;
};
//ww:module parse
// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww.
package parse;
@@ -9521,6 +9646,7 @@ fn parsestmt(p: *parser) *node = {
};
//ww:module-reset
// lib/ww/typ.ww — port of cmd/wcc/type.c.
//
// Status: full structural port. The C version uses module-globals for
@@ -10137,6 +10263,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
return true; // primitives match by kind alone
};
//ww:module-reset
// lib/ww/sym.ww — port of cmd/wcc/sym.c.
//
// Per-scope hashtable, chained to the parent. Lookup walks up.
@@ -10472,6 +10599,7 @@ export fn scopesamekeysym(s: *scope, name: str, mod: str) *sym = {
return nil;
};
//ww:module-reset
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
//
// Status: name-resolution + primitive-type seeding only. Full type
@@ -10638,14 +10766,48 @@ fn declmod(file: *node, d: *node) str = {
if (file == nil) { return empty; };
let u: *node = file.list;
for (u != nil) {
// M1 #22: a decl is imported iff some `use` directive's full
// dotted import path equals the decl's module (now the path).
// Single-level packages have usepath == leaf so this is
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
if (u.kind == nkind.N_USE) {
if (streq(u.str, d.nmod)) { return d.nmod; };
if (streq(u.usepath, d.nmod)) { return d.nmod; };
};
u = u.next;
};
return empty;
};
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
// to the full dotted import path it binds (`encoding.utf8`), for the
// module-qualified resolution and codegen hint (M1 #22). Single-level
// packages have usepath == alias so the result is unchanged. The
// current tree has one occurrence per leaf, so the map is unambiguous.
fn usepathfor(file: *node, alias: str) str = {
let empty: str;
if (file == nil) { return empty; };
if (alias.len == 0) { return empty; };
let u: *node = file.list;
for (u != nil) {
if (u.kind == nkind.N_USE) {
if (streq(u.str, alias)) {
if (u.usepath.len != 0) { return u.usepath; };
return u.str;
};
};
u = u.next;
};
return empty;
};
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
fn modkeyfor(c: *checker, alias: str) str = {
let mk: str = usepathfor(c.file, alias);
if (mk.len == 0) { return alias; };
return mk;
};
// srcimports — does the source file that contributed decl-module
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
// `modtag.len == 0` means primary, matching declmod's empty-str
@@ -10662,7 +10824,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
// module bareword and lib/fmt's own
// `fn bsprintf(fmt: str, ...)` is not a shadow.
if (u.nmod.len > 0) {
if (streq(u.nmod, u.str)) {
if (streq(u.nmod, u.usepath)) {
u = u.next;
continue;
};
@@ -10751,7 +10913,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
// pulls lack import->file->symbol provenance). Message byte-identical
// to cstage check.c.
if (k == nkind.N_USE) {
if (mod.len != 0 && streq(nm, mod)) {
// M1 #22: self-import ⟺ the imported path equals the use's own
// (owning) module path. Compares paths, not leaves.
if (mod.len != 0 && streq(d.usepath, mod)) {
cerr("self-import: package '"); cerr(mod);
cerr("' cannot import itself\n"); c.errs += 1i32;
};
@@ -10960,7 +11124,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
let leaf: str;
leaf.ptr = nm.ptr + (dot + 1): u64;
leaf.len = nm.len - (dot + 1);
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
};
};
};
@@ -11358,7 +11522,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
@@ -11609,7 +11773,7 @@ fn scruttype(c: *checker, e: *node) *node = {
if (e.kind == nkind.N_DOT) {
if (e.lhs == nil) { return nil; };
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
if (s == nil) { return nil; };
if (s.decl == nil) { return nil; };
return s.decl.lhs;
@@ -12148,7 +12312,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
if (k == nkind.N_DOT) {
if (n.lhs == nil) { return false; };
if (n.lhs.kind != nkind.N_IDENT) { return false; };
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
if (s == nil) { return false; };
if (s.skind != skind.SK_DEF) { return false; };
if (s.decl == nil) { return false; };
@@ -13265,7 +13429,7 @@ fn unoptype(c: *checker, e: *node) *node = {
// cstage's actual acceptance reason.
if (e.lhs.kind == nkind.N_DOT) {
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) {
if (fs.decl != nil) {
@@ -13923,7 +14087,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// global-leaf path) and harec check_autodereference
// (ref/harec/src/check.c:1566-1581).
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
};
};
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
@@ -13983,7 +14147,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
// qualified resolution and the lenient checker policy
// keeps the silent miss documented at scruttype L656.
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
if (fs != nil) { if (fs.decl != nil) {
// #34: a module-qualified bare fn rvalue `mod.fn` types as
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
@@ -15840,7 +16004,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
};
if (ms != nil) {
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
if (fs != nil) {
if (fs.skind == skind.SK_FN) { return fs.decl; };
};
@@ -16794,29 +16958,25 @@ export fn checkfile(c: *checker, file: *node) void = {
d = d.next;
};
// Program-global, name-only, cross-module uniqueness on `main`.
// `main` lowers to ONE bare entry symbol, so a second top-level
// decl named `main` (any kind, any package) collides with the
// entry at link time — today a silent segfault / link-fail in
// both stages. The (name, module) duplicate rejects in installtop
// read a cross-package `foo.main` and the bare entry as distinct,
// so they miss this. Correct multi-main mangling (entry stays bare,
// the rest qualify) is deferred (task #32); reject loudly meanwhile
// (rule 7). Walks USER decls only — runs before the -T synth main
// is appended below — so a hosted-test build never false-counts.
// Twin of cmd/wcc/check.c.
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
// the ROOT-unit main (imported==0) — it alone lowers to the bare
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
// collision by construction (#31). Two ROOT entries still collide on
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
let firstmain: *node = nil;
let mm: *node = file.list;
for (mm != nil) {
let ismain: bool = (mm.kind == nkind.N_FNDECL
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
if (ismain) {
if (ismain && mm.imported == 0) {
if (firstmain == nil) {
firstmain = mm;
} else {
cerr(mm.file);
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
c.errs += 1;
};
};
@@ -17146,6 +17306,7 @@ export fn checkfile(c: *checker, file: *node) void = {
};
//ww:module io
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
//
// No closures, no methods. A `stream` is a pointer to a `vtable` of
@@ -17173,6 +17334,7 @@ export type eof = void;
// underlying buffer-length type is i32 today.
export type underread = !i32;
//ww:module io
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
//
// The single io stream surface (the fold-eFinal collapse retired the
@@ -17396,6 +17558,7 @@ export fn empty() stream = {
return &_empty_vt;
};
//ww:module errors
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
//
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
@@ -17532,6 +17695,7 @@ fn rt_strerror(op: *opaque_data) str = {
return os.strerror(*e);
};
//ww:module io
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).
@@ -17617,6 +17781,7 @@ export type closer = fn(s: stream) (void | error);
// `stream` directly, mirroring the reader/writer/closer aliases above.
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
//ww:module memio
// memio — in-memory io stream. Project #94 fold-eFinal.
//
// Hare's memio:: surface, drop underscores. Two flavours behind a
@@ -17922,6 +18087,7 @@ export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
return r;
};
//ww:module-reset
// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww.
//
// General helpers used across cgenexpr / cgenstmt / cgendecl:
@@ -20091,10 +20257,14 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: the embedded qualifier (`utf8`) is the use ALIAS;
// the struct's smod is now the dotted import PATH
// (`encoding.utf8`). Map alias→path before comparing.
let pkgmod: str = usehint(c, pkg);
let b: *structinfo = c.structs;
for (b != nil) {
if (streq(b.sname, leaf)) {
if (streq(b.smod, pkg)) {
if (streq(b.smod, pkgmod)) {
return b;
};
};
@@ -23474,6 +23644,7 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
};
//ww:module-reset
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
//
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
@@ -27782,10 +27953,10 @@ fn cgdot(c: *cgen, n: *node) void = {
// lhs.str is the explicit module hint so a same-leaf
// def in another module (head of c.fnrets) can't shadow
// the explicit qualifier (#17 N_DOT-arm omission audit).
let frt: *node = fnretlookupmod(c, fld, lhs.str);
let frt: *node = fnretlookupmod(c, fld, usehint(c, lhs.str));
if (frt != nil) {
emitline("\tLEAQ\t");
emitfnname(c, fld, lhs.str);
emitfnname(c, fld, usehint(c, lhs.str));
emitline("(SB), AX\n");
return;
};
@@ -27797,7 +27968,7 @@ fn cgdot(c: *cgen, n: *node) void = {
// the explicit module hint — a 3rd-module qualifier
// `alpha.MSG` from gamma needs alpha (not c.curmod)
// to beat a head-of-c.defs beta.MSG collision (#11).
let drhs: *node = deflookuprhsmod(c, fld, lhs.str);
let drhs: *node = deflookuprhsmod(c, fld, usehint(c, lhs.str));
if (drhs != nil) {
if (drhs.kind == nkind.N_STRLIT) {
let bytes: str = drhs.str;
@@ -27818,11 +27989,11 @@ fn cgdot(c: *cgen, n: *node) void = {
// threads lhs.str via emitfnname.
if (streq(mqop, "MOVQ")) {
emitline("\tMOVQ\t");
emitsymnamehint(c, fld, lhs.str);
emitsymnamehint(c, fld, usehint(c, lhs.str));
emitline("(SB), AX\n");
} else {
emitline("\tLEAQ\t");
emitsymnamehint(c, fld, lhs.str);
emitsymnamehint(c, fld, usehint(c, lhs.str));
emitline("(SB), CX\n");
emitline("\t");
emitline(mqop);
@@ -28702,10 +28873,10 @@ fn cgun(c: *cgen, n: *node) void = {
// pre-#149 gap (file as #150-family).
if (!isletvar(c, basenm) && !deflookup(c, basenm)) {
let fld: str = opnd.str;
let frt: *node = fnretlookupmod(c, fld, basenm);
let frt: *node = fnretlookupmod(c, fld, usehint(c, basenm));
if (frt != nil) {
emitline("\tLEAQ\t");
emitfnname(c, fld, basenm);
emitfnname(c, fld, usehint(c, basenm));
emitline("(SB), AX\n");
return;
};
@@ -28714,7 +28885,7 @@ fn cgun(c: *cgen, n: *node) void = {
// &aa.v takes aa's global, not a
// same-leaf collision.
emitline("\tLEAQ\t");
emitsymnamehint(c, fld, basenm);
emitsymnamehint(c, fld, usehint(c, basenm));
emitline("(SB), AX\n");
return;
};
@@ -31703,7 +31874,7 @@ fn cgcall(c: *cgen, n: *node) void = {
hint.len = 0;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
hint = callee.lhs.str;
hint = usehint(c, callee.lhs.str);
};
};
emitfnname(c, calleename, hint);
@@ -36013,6 +36184,7 @@ fn cgassign(c: *cgen, n: *node) void = {
//ww:module-reset
// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww.
//
// cgstmt is a thin dispatcher over n.kind; each branch defers to a
@@ -40362,6 +40534,7 @@ fn cgcontinue(c: *cgen, n: *node) void = {
//ww:module-reset
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
//
// Houses the top-level emission glue:
@@ -40966,9 +41139,17 @@ fn cgfn(c: *cgen, fn_: *node) void = {
// Emit the TEXT label via emitfnname so the def site picks up the
// same skip rule (FFI / `main` / empty-module) and the same module
// hint (this fn's own module) that the call sites use.
// hint (this fn's own module) that the call sites use. M1 #32: the
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
// bare directly, mirroring collectmods' skip; without this its
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
// onto an IMPORTED package's now-registered `pkg.main`.
emitline("TEXT ");
emitfnname(c, fn_.str, fn_.nmod);
if (streq(fn_.str, "main") && fn_.imported == 0) {
emitbytes(fn_.str.ptr, fn_.str.len: u64);
} else {
emitfnname(c, fn_.str, fn_.nmod);
};
emitline(",$");
emitint(frame: i64);
emitline("\n");
@@ -41015,6 +41196,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
emitletdataw(c, file);
};
//ww:module-reset
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
//
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
@@ -41140,10 +41322,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *aliasent = c.aliases;
for (b != nil) {
if (streq(b.aname, leaf)) {
if (streq(b.amod, pkg)) {
if (streq(b.amod, pkgmod)) {
return b.target;
};
};
@@ -41328,10 +41513,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
let leaf: str;
leaf.ptr = name.ptr + ((i + 1): u64);
leaf.len = name.len - (i + 1);
// M1 #22: map the embedded use ALIAS (`utf8`) to the
// dotted import PATH the decl's module now carries.
let pkgmod: str = usehint(c, pkg);
let b: *enumtype = c.enums;
for (b != nil) {
if (streq(b.ename, leaf)) {
if (streq(b.emod, pkg)) {
if (streq(b.emod, pkgmod)) {
return b;
};
};
@@ -41502,6 +41690,9 @@ type cgen = struct {
enums: *enumtype,
mods: *modent, // fn (any export status) + non-exported
// let/def/type decls → originating module
uses: *modent, // M1 #22: N_USE alias → dotted import path,
// for the qualified-ref codegen hint
// (mname=alias, nmod=path)
lets: *letvar, // top-level mutable scalar `let` bindings
fnname: str,
curmod: str, // current fn's `// MODULE: foo` directive (len=0
@@ -43877,7 +44068,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
// the leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (ev.lhs.kind == nkind.N_DOT) {
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
} else {
emitfnname(c, ev.lhs.str, c.curmod);
};
@@ -44064,7 +44255,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
// leaf with the MODULE ident; same-module `&fn`
// stays on curmod.
if (r.lhs.kind == nkind.N_DOT) {
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
} else {
emitfnname(c, r.lhs.str, c.curmod);
};
@@ -44535,11 +44726,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
// wrote (`utf8`); fn decls register f.fmod under the dotted import
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
// matches its own module instead of falling back to the name-only
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
// utf8.next) otherwise wins, resolving a match scrutinee to the
// caller's union and collapsing arms 2+. usehint is idempotent on a
// path / c.curmod (returns the input when no `use` matches), so the
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
// c.curmod callers are unaffected. The choke-point twin of the
// struct/alias/enum usehint splitters (cgen.ww:128/319,
// cgenutil.ww:2173) — closes the whole fnret class by construction.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.rtype; };
if (streq(f.fmod, mk)) { return f.rtype; };
};
f = f.frnext;
};
@@ -44599,11 +44803,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
// matching module is registered — mirrors aliaslookup's two-pass shape
// (cgen.ww:75, fixed in #27).
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
if (mod.len > 0) {
// M1 #22 (#199b): map import alias -> dotted path, identical to
// fnretlookupmod (the param-side twin). usehint is idempotent on a
// path / c.curmod so existing callers are unaffected.
let mk: str = usehint(c, mod);
if (mk.len > 0) {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, mod)) { return f.params; };
if (streq(f.fmod, mk)) { return f.params; };
};
f = f.frnext;
};
@@ -44783,9 +44991,17 @@ type modent = struct {
fn collectmods(c: *cgen, file: *node) void = {
c.mods = nil;
c.uses = nil;
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
c.uses = um;
};
};
// Mirror collectfnrets' shape exactly (plain prepend in one
// branch). Earlier nested-if/early-return variants tickled a
// wwstage cgen bug that dropped most prepends.
@@ -44803,7 +45019,9 @@ fn collectmods(c: *cgen, file: *node) void = {
a = a.next;
};
if (!isffi) {
if (!streq(d.str, "main")) {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
@@ -44850,6 +45068,19 @@ fn modlookup(c: *cgen, name: str) str = {
return empty;
};
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
m = m.mnext;
};
return alias;
};
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
// preferring entries where module matches `hint`; falls back to the
// first leaf-name match when nothing matches the hint (legacy single-
@@ -45041,6 +45272,7 @@ export fn fargregname(i: i32) str = {
return "?";
};
//ww:module-reset
// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c.
//
// Reads a .ww file, runs the ww-side lexer, prints tokens through